from typing import Optional
from matplotlib import pyplot as plt
import copy
import numpy as np
import serial
import sys
from cion.core.gates import *
import cion.core.data as cion_data
from cion.core.gui import scan_lines_widgets, scan_generator_pop, LiveHistogramWidgets, LiveLinesWidgets, LiveBarsWidgets,run_scan_with_popup
from pathlib import Path
import math
import concurrent.futures
import time

### 下面是cion调用硬件接口的部分
### 对于特定的实验环境，需要用户自主地实现这些硬件接口,以顺利调用相应的硬件设备
import cion.core.hardware.serial_comm as hardwarecomm # FPGA串口通信接口（如有必要）
import cion.core.hardware.awg_control as awgcomm # AWG以太网通信（如有必要）

# 求最大公约数
def lcm(a, b):
    return abs(a*b) // math.gcd(a, b)

# 定义空序列，供doppler和pumping使用
empty = []

# all 在每次定义新的Experiment对象时都会对应改变为[0, 1, ..., ion_number-1]
# 比如: exp1 = Experiment(ion_number = 5, ...)时，all会自动变为[0,1,2,3,4]
all = []

class Global_Setting:
    """
    全局设定，用于设定当前cion是出于debug模式还是实验模式，如果是debug模式，则会假装PC连接了底层各类硬件，
    但运行sequence时不真实操控这些硬件
    并且将“实验文件”保存至当前目录下的test_datafile_dir文件夹里
    """
    __debug_mode: bool = False
    #__debug_mode: bool = True
    __ser = None

    @classmethod
    def get_ser(cls):
        return cls.__ser

    @classmethod
    def get_debug_mode(cls):
        return cls.__debug_mode

    @classmethod
    def set_ser(cls, ser):
        __ser = ser

    @classmethod
    def set_debug_mode(cls, mode:bool):
        cls.__debug_mode = mode


# AWG相关的dll调用接口
#=======
#    ser = serial.Serial(
#                port='COM5',
#                baudrate=4000000,
#                parity=serial.PARITY_NONE,
#                stopbits=serial.STOPBITS_TWO,
#                bytesize=serial.EIGHTBITS
#                )

def load_sequencies(file_path):
    """
    从文件中加载sequence，但是没人用过

    Parameters
    ----------
    file_path : _type_
        _description_
    """
    seq_file = Path(file_path)
    if seq_file.is_file():
        execfile(seq_file)
    return

def split_array(arr, n):
    remainder = len(arr) % n
    if remainder != 0:
        arr = arr[:-remainder]
    return arr.reshape(-1, n)

def flatten_list(input_list):
    """
    把输入的列表扁平化变成一个单一的列表

    :param input_list: 元素是列表的列表
    :return: 扁平化后的列表
    """
    # 仅将列表扁平化
    flattened_list = [item for sublist in input_list for item in sublist]
    
    return flattened_list


### An example to test importlib.reload()
class A:
    def __init__(self):
        self.aa = 1
        print('ab')



class Experiment:
    """
        用于离子阱实验的主类，每次开始新的实验都应该创建一个Experiment实例。
        例如：exp = Experiment(2, chapter_dict = FPGA_dict, port = 'COM3')
        表示开始一次实验，此实验中离子数为2，FPGA对应的chapter表为FPGA_dict, 端口号为COM3
    """
    def __init__(self, ion_number = -1, rf_pick = 854, melt_status = False, eta = 0.098, chapter_dict = None, port = None, awg_address = None, camera=None):
        """
         ion_number: 当前实验状态下的离子数，可以在初始化时不定义，后续实验中用EMCCD探测离子数
         rf_pick: 不清楚是什么参数
         melt_status： 检验离子是否融化，但是该功能没有完成
         eta ：lamb-dick 参数，似乎不用管
         chapter_dict：导入你对各个控制模块在FPGA接口上的位置编号
         port : 串口号，如'COM3',是FPGA的串口号
         awg_address: 用以太网连接方式连接的AWG的IP地址，格式是'TCPIP::192.168.2.10::inst1::INSTR'
         camera: 用CameraDetection(target_temperature=-80, path="./cion/core/emccd/atmcd64d.dll")定义好的相机实例
         channel：可能是多通道PMT使用的参数，2d台子不使用
         clock_period ：不清楚是什么参数
         repeat ：一个sequence的重复次数，比如做同样一个实验序列重复多少次，每重复一次得到一张图像或一个光子count数据
         ext_trig ：外部触发标识，ext_trig = 0是启动line trigger检验50hz噪声与相干时间的关系，=1就是正常做实验
         bytes_returned ：FPGA返回的字节数，不同的FPGA可能有不同的返回字节数，请根据实际情况设置
         
         awg_switch: awg开关，如果awgd_address不为空，则该参数为True，说明连接上了AWG
         camera_switch ：camera开关，如果camera不为空，则该参数为True，说明连接上了相机
         threshold ：阈值，相机单次探测通过拟合得到的光子数如果高于阈值则为亮1，低于阈值则为暗0
         state_flag ：态标志，指示画图等地方是画态的布居数还是单个离子的亮暗比例

        # debug模式下：
        ### 假装PC连接了底层各类硬件，并且将“实验文件”保存至当前目录下的test_datafile_dir文件夹里
        # 实验模式下：
        ### 结束上一次实验的串口连接，并且为本次实验新建串口通讯
        ### 连接awg的以太网地址
        """
        self.ion_number = ion_number
        self.rf_pick = rf_pick
        self.mode_frequency = np.random.random(ion_number)
        self.melt_status = melt_status
        self.Lamb_Dicke_parameter = eta
        self.channel = [2,4,0,6]
        self.clock_period = 5E-3
        self.repeat = 100
        self.ext_trig = 1
        #Different version of FPGA could have different bytes_returned. If FPGA doesn't work as expected, please check whether bytes_returned is correcly setted.
        self.bytes_returned = 12
        self.port = port
        self.awg_switch = False
        self.camera_switch = False
        self.threshold = None
        self.state_flag = False

        print(
            """                                               
 ██████╗  ██╗   ██████╗   ███╗   ██╗
██╔════╝  ██║  ██╔═══██╗  ████╗  ██║
██║       ██║  ██║   ██║  ██╔██╗ ██║
██║       ██║  ██║   ██║  ██║╚██╗██║
╚██████╗  ██║  ╚██████╔╝  ██║ ╚████║
 ╚═════╝  ╚═╝   ╚═════╝   ╚═╝  ╚═══╝
══════════════════════════════════════════════════════════════
            """
        )
        if awg_address != None:
            self.awg_switch = True
        if camera != None:
            self.camera_switch = True
            self.camera = camera
            self.camera_parameters = {"exposure_time":0.0005, "h_redundancy":10, "v_redundancy":10, "hbin":1, "vbin":1}
            print("""
Camera is enabled.
══════════════════════════════════════════════════════════════
            """)

        self.__chapter_dict = None
        if chapter_dict != None:
            self.__chapter_dict = self.__prev_process_chapter(chapter_dict)
        else:
            print('Warning! You did not specify the chapter data for this experiments, the FPGA utility will be deactivated.')

        # 一次实验可以创建多个Sequence，全部存储在seq_list里面
        self.seq_list = []
        self.last_sequence = Sequence(ion_number=ion_number, chapter_dict=self.__chapter_dict)
        global all
        all = []
        [all.append(i) for i in range(ion_number)]
        self.all = [i for i in all]

        assert(isinstance(ion_number, int) and ion_number >= 1)
        assert(isinstance(melt_status, bool))

        # debug模式下：
        ### 假装PC连接了底层各类硬件，并且将“实验文件”保存至当前目录下的test_datafile_dir文件夹里
        # 实验模式下：
        ### 结束上一次实验的串口连接，并且为本次实验新建串口通讯
        ### 连接awg的以太网地址
        if Global_Setting.get_debug_mode() == True:
            self.data = cion_data.DataDoc(disk_path=r'test_datafile_dir',debug_mode=Global_Setting.get_debug_mode())
            print("/********************\nCaution! You are using debug mode. \
This enables you to test some functions locally. But you cannot access the actual hardware in this mode.\n********************/")
        else:
            self.data = cion_data.DataDoc(debug_mode=Global_Setting.get_debug_mode())
            if port != None:
                if Global_Setting.get_ser() != None:
                    if Global_Setting.get_ser().isOpen():
                        Global_Setting.get_ser().close()
                        time.sleep(1.5)
                this_ser = serial.Serial(
                    port=port,
                    baudrate=4000000,
                    parity=serial.PARITY_NONE,
                    stopbits=serial.STOPBITS_TWO,
                    bytesize=serial.EIGHTBITS
                )
                Global_Setting.set_ser(this_ser)
                self.ser = this_ser
            if awg_address != None:
                self.awg_interface = awgcomm.AWG_Control(awg_address)

################################## TEMP ###############################################
    # def sweep_generator_temp(self,n,sequence,position):
    #         i = 0
    #         x = np.array([0.5] * n)
    #         #import time
    #         #start_time = time.time()
    #         if sequence.awg_trigger==True:
    #             sequence.perform_sequence_awg()
    #             self.awg_interface.send_data(sequence.awg_data[0], loop=0)
    #             #time.sleep(1)
    #         while True:
    #             counts = self.run_once(sequence=sequence)
    #             counts = counts[position::32]
    #             counts = np.array([sum(counts)/self.repeat]*n)
    #             i += 1
    #             yield i,counts
    #
    # def sweep_temp(self, repeat=100, steps=100, fps=64, win_size=200, sequence=None, position=16):
    #     if sequence==None:
    #         sequence=self.last_sequence
    #     sequence.regenerate_sequence()
    #     generator = self.sweep_generator_temp(self.ion_number, sequence=sequence, position=position)
    #     reader, viewer, button = scan_lines_widgets(  # 不推荐用 scan_lines_widgets，请改用 LiveLinesWidgets 并返回 widgets 对象
    #         n=self.ion_number,
    #         hist_bins=10,
    #         producer=lambda: next(generator),
    #         win_size=win_size,
    #         sampling_rate=64)
    #     return
    # def sweep(self, repeat=100, steps=100, win_size=200, sequence=None, fps=64):
    #     if sequence==None:
    #         sequence=self.last_sequence
    #     sequence.regenerate_sequence()
    #     generator = self.sweep_generator(n=self.ion_number, sequence=sequence)
    #     return LiveLinesWidgets(
    #         producer=generator,
    #         produce_interval=1/64,
    #         bins=10, win=win_size)
    # def sweep_generator(self,n,sequence):
    #     i = 0
    #     x = np.array([0.5] * n)
    #     #import time
    #     #start_time = time.time()
    #     if sequence.awg_trigger==True:
    #         sequence.perform_sequence_awg()
    #         self.awg_interface.send_data(sequence.awg_data[0], loop=0)
    #         #time.sleep(1)
    #     while True:
    #         counts = self.run_once(sequence=sequence)
    #         counts = np.array([sum(counts)/self.repeat]*n)
    #         i += 1
    #         yield i,counts
    # def sweep_generator_histogram(self, n, sequence):
    #     #import time
    #     #start_time = time.time()
    #     if sequence.awg_trigger==True:
    #         sequence.perform_sequence_awg()
    #         self.awg_interface.send_data(sequence.awg_data[0], loop=0)
    #         #time.sleep(1)
    #     while True:
    #         counts = np.mean(np.array(self.run_once(sequence=sequence)).reshape(32,self.repeat),1)
    #         yield counts
    def sweep_generator_histogram(self, ionnumber, sequence, select_ion_index):
        """
        用于把sequence测量结果返回并画出直方图的生成器，运行序列并返回每个离子的亮暗数据，
        通过select_ion_index选择需要画的离子的索引，返回一个生成器

        Parameters
        ----------
        ionnumber : int
           离子数
        sequence : Sequence
            启动扫描时使用的sequence序列
        select_ion_index : list
            选择需要画的离子的索引
        Yields
        ------
        counts : np.ndarray
            每个离子的亮暗数据
        """
        if sequence.awg_trigger==True:
            sequence.perform_sequence_awg()
            self.awg_interface.send_data(sequence.awg_data[0], loop=0)
        while True:
            counts =np.array(sum(self.run_once(sequence=sequence))/self.repeat).transpose()
            yield counts[select_ion_index]
    def sweep_histogram(self,select_ion_index=None, fps=32, sequence=None):
        """
        用于画出直方图的函数，运行序列并返回每个离子的亮暗数据，通过select_ion_index选择需要画的离子的索引，返回一个widgets对象

        Parameters
        ----------
        select_ion_index : list, optional
            选择需要画的离子的索引，默认画所有离子的亮暗比例，不输入时默认画所有的
        fps : int, optional
            刷新率, by default 32
        sequence : Sequence, optional
            启动扫描时使用的sequence序列, by default None

        Returns
        -------
        LiveBarsWidgets
            直方图widgets对象
        """
        if sequence==None:
            sequence=self.last_sequence
        sequence.regenerate_sequence()
        if select_ion_index is None:
            select_ion_index = np.arange(self.ion_number)
        arr = np.asarray(select_ion_index)
        assert np.issubdtype(arr.dtype, np.integer), "select_ion_index should be an array of integers"
        arr = np.sort(arr)
        return LiveBarsWidgets(
            producer=self.sweep_generator_histogram(ionnumber=self.ion_number, sequence=sequence,select_ion_index=arr),  # here you can give either an iterator (generator) or a callable
            frame_interval=1 / fps,  # the widget will try to update the ui with this interval (seconds)
        )
    
################################## TEMP ###############################################

    def update_chapter_dict(self, chapter_dict):
        """
        #TODO 
        Parameters
        ----------
        chapter_dict : _type_
            _description_
        """
        self.__chapter_dict = self.__prev_process_chapter(chapter_dict)

    def reopen_serial_port(self):
        """
        重新开启串口连接
        #TODO 根本用不了
        """
        assert self.port != None
        if Global_Setting.get_ser() != None:
            if Global_Setting.get_ser().isOpen():
                Global_Setting.get_ser().close()
        this_ser = serial.Serial(
            port=self.port,
            baudrate=4000000,
            parity=serial.PARITY_NONE,
            stopbits=serial.STOPBITS_TWO,
            bytesize=serial.EIGHTBITS
        )
        Global_Setting.set_ser(this_ser)
        self.ser = this_ser

    # 在Experiment里创建一个新的Sequence实例
    def new_sequence(self):
        """
        创建一个新的Sequence实例，并将其加入seq_list中，并将last_sequence指向该实例

        Returns
        -------
        Sequence
            新的Sequence实例
        """
        seq = Sequence(ion_number=self.ion_number, chapter_dict=self.__chapter_dict)
        self.seq_list.append(seq)
        self.last_sequence = seq
        return seq

    def check_sequence(self, sequence):
        """
        #TODO

        Parameters
        ----------
        sequence : _type_
            _description_

        Returns
        -------
        _type_
            _description_
        """
        if sequence == None:
            sequence = self.last_sequence
        return sequence

    def __prev_process_chapter(self, chapter_dict):
        """
        预处理chapter_dict，将其转换为适合FPGA的格式

        Parameters
        ----------
        chapter_dict : _type_
            _description_

        Returns
        -------
        _type_
            _description_
        """
        out_dict = {}
        for k,v in chapter_dict.items():
            st = v.replace(' ','')
            chapter_data_list = []
            while st != '':
                lbracket_pos = st.find('[')
                rbracket_pos = st.find(']')
                if lbracket_pos == 0:
                    comma_pos = st.find(',')
                    assert comma_pos == 25
                    assert rbracket_pos > 26
                    sub_str_1 = st[1:25]
                    sub_str_2 = st[26:rbracket_pos]
                    chapter_data_list.append((sub_str_1, float(sub_str_2)))
                    st = st[rbracket_pos+2:]
                else:
                    chapter_data_list.append(st[0:24])
                    st = st[25:]
            assert out_dict.get(k) == None
            out_dict[k] = chapter_data_list
        return out_dict

    def set_path_prefix(self, prefix):
        """
        设置数据保存路径前缀

        Parameters
        ----------
        prefix : str
            数据保存路径前缀
        """
        self.path_prefix = prefix

    def set_ion_number(self, ion_number):
        """
        修改离子数

        Parameters
        ----------
        ion_number : int
            新设定的离子数
        """
        self.ion_number = ion_number
        all.clear()
        [all.append(i) for i in range(ion_number)]
        if ion_number > len(self.channel):
            print("Alert! The ion number in current setup is larger than the predefined channel numbers.\n Try using\
            member function \'set_channel\' to reset channel number.")

    def set_channel(self, channel):
        '''
        #TODO
        reset channel and convert matrix
        '''
        assert(self.ion_number == len(channel))
        self.channel = channel

        m = 2*self.ion_number-1
        self.convert_matrix = np.zeros((self.ion_number, m), dtype=int)
        for i in range(self.ion_number):
            self.convert_matrix[i][channel[i]] = 1

    def sweep_generator(self,n,sequence):
        """
        线条式扫描启动器，返回一个生成器，每次调用生成器返回一个离子的亮暗数据

        Parameters
        ----------
        n : int
            离子数
        sequence : Sequence
            启动扫描时使用的sequence序列
        Yields
        ------
        tuple
            离子的亮暗数据
        """
        i = 0
        x = np.array([0.5] * n)
        if sequence.awg_trigger==True:
            sequence.perform_sequence_awg()
            self.awg_interface.send_data(sequence.awg_data[0], loop=0)
        while True:
            counts = self.run_once(sequence=sequence)
            counts = np.array([sum(counts)/self.repeat]*n)
            i += 1
            yield i,counts

    def sweep(self, repeat=100, steps=100, win_size=200, sequence=None, fps=64):
        """
        线条式扫描启动器，返回一个widgets对象，每次调用widgets对象返回一个离子的亮暗数据

        Parameters
        ----------
        repeat : int, optional
            , by default 100
        steps : int, optional
           , by default 100
        win_size : int, optional
            , by default 200
        sequence : Sequence, optional
            , by default None
        fps : int, optional
            , by default 64

        Returns
        -------
        LiveLinesWidgets
            线条式扫描widgets对象
        """
        if sequence==None:
            sequence=self.last_sequence
        sequence.regenerate_sequence()
        generator = self.sweep_generator(n=self.ion_number, sequence=sequence)
        return LiveLinesWidgets(
            producer=generator,
            produce_interval=1/64,
            bins=10, win=win_size)

    def print_info_all():
        """
        #TODO
        """
        #print(global_parameters)
        #print(sequences)
        pass

    def print_info_global():
        """
        $TODO
        """
        print("debug_mode:", Global_Setting.get_debug_mode())
        print("serial_port:", Global_Setting.get_ser())
        pass

    def conditional_run_once(self, mode, repeat=100, sequence = None):
        """
        #TODO

        Parameters
        ----------
        mode : _type_
            _description_
        repeat : int, optional
            _description_, by default 100
        sequence : _type_, optional
            _description_, by default None
        """
        if sequence == None:
            sequence = self.last_sequence
        # debug模式下不会访问硬件
        if Global_Setting.get_debug_mode() == True:
            print(sequence.perform_sequence())
        # 实验模式下才会真正与硬件交互
        else:
            self.run_once(repeat=repeat)
    


    
    def time_scan_generator(self, scan_list, ranges, sequence, _update_sequence=None,checkbeforerun=False):
        """
        时间扫描启动器，返回一个生成器，每次调用生成器

        scan_list: a list of labels, like '['label1', 'rx1', 'rx2', ...]'
        ranges: duration list to be scanned
        (Old definition: ranges: a tuple, its length equivalent to scan_list's length)

        Parameters
        ----------
        scan_list : 
            _description_
        ranges : _type_
            _description_
        sequence : _type_
            _description_
        _update_sequence : _type_, optional
            _description_, by default None

        Returns
        -------
        _type_
            _description_
        """
        n = self.ion_number
        x = np.array([0.5] * n)
        #assert len(scan_list) == len(ranges)
        #Independently scan durations
        # print(isinstance(ranges[0],(np.ndarray,list)))
        if isinstance(ranges[0],(np.ndarray,list)):
            scan_ranges = range(len(ranges[0]))
            def update_duration(i):
                if checkbeforerun:
                    rightnowionnumber=self.camera.fastcheckionnumber(img_num=3,debug=0)
                    if rightnowionnumber!=self.ion_number:
                        print("detected",rightnowionnumber)
                        print("seted",self.ion_number)
                        raise ValueError("The ion number currentis not equal to the ion number exp set.")
                for label, durations in zip(scan_list, ranges):
                    sequence.set_parameters(label=label, duration=durations[i], batch_mode=True)

                sequence.regenerate_sequence()
                if sequence.awg_trigger==True:
                    if _update_sequence != None:
                        _update_sequence(durations[i])
                    sequence.perform_sequence_awg()
                    self.awg_interface.send_data(sequence.awg_data[0], loop=0)
                    
                #Run experiment
                raw_counts = self.run_once(sequence=sequence)
                counts = np.array([sum(raw_counts)/self.repeat])
                self.data.append_raw_data(i, raw_counts)
                return counts[-1]
        #Globally scan durations
        else:
            scan_ranges = ranges
            def update_duration(duration):
                if checkbeforerun:
                    rightnowionnumber=self.camera.fastcheckionnumber(img_num=3,debug=0)
                    if rightnowionnumber!=self.ion_number:
                        print("detected",rightnowionnumber)
                        print("seted",self.ion_number)
                        raise ValueError("The ion number currentis not equal to the ion number exp set.")
                if isinstance(scan_list,(np.ndarray,list)):
                    for label in scan_list:
                        sequence.set_parameters(label=label, duration=duration, batch_mode=True)
                else:
                    assert isinstance(scan_list,str)
                    sequence.set_parameters(label=scan_list, duration=duration, batch_mode=True)
                sequence.regenerate_sequence()
                if sequence.awg_trigger==True:
                    if _update_sequence != None:
                        _update_sequence(duration)
                    sequence.perform_sequence_awg()
                    self.awg_interface.send_data(sequence.awg_data[0], loop=0)
                    
                #Run experiment
                # np.sum(np.array([[0,1],[2,3]]),axis=0)
                # counts = np.array([sum(np.array([[0,1],[2,3]]))/100])
                # print(counts)
                # print(counts[-1])
                #counts[-1]刚好返回一个概率数组result[ionintex]=probablity_ionindex
                raw_counts = self.run_once(sequence=sequence)
                counts = np.array([sum(raw_counts)/self.repeat])
                self.data.append_raw_data(duration, raw_counts)
                # print("时间和返回的数据结果是",duration,raw_counts,counts)
                return counts[-1]
        # sequence updated
        # now please get data for the expermients
        #TODO
        return scan_ranges, update_duration
    
    def time_scan_generator_state_populartion(self, scan_list, ranges, sequence, _update_sequence=None,checkbeforerun=False):
        '''
        scan_list: a list of labels, like '['label1', 'rx1', 'rx2', ...]'
        ranges: duration list to be scanned
        (Old definition: ranges: a tuple, its length equivalent to scan_list's length)
        '''
        n = self.ion_number
        x = np.array([0.5] * n)
        #assert len(scan_list) == len(ranges)
        #Independently scan durations
        # print(isinstance(ranges[0],(np.ndarray,list)))
        if isinstance(ranges[0],(np.ndarray,list)):
            scan_ranges = range(len(ranges[0]))
            def update_duration(i):
                if checkbeforerun:
                    rightnowionnumber=self.camera.fastcheckionnumber(img_num=3,debug=0)
                    if rightnowionnumber!=self.ion_number:
                        print("detected",rightnowionnumber)
                        print("seted",self.ion_number)
                        raise ValueError("The ion number currentis not equal to the ion number exp set.")
                for label, durations in zip(scan_list, ranges):
                    sequence.set_parameters(label=label, duration=durations[i], batch_mode=True)

                sequence.regenerate_sequence()
                if sequence.awg_trigger==True:
                    if _update_sequence != None:
                        _update_sequence(durations[i])
                    sequence.perform_sequence_awg()
                    self.awg_interface.send_data(sequence.awg_data[0], loop=0)
                    
                #Run experiment
                raw_counts = self.run_once(sequence=sequence)
                # Get the shape of the result array
                repeat, ionnumber = raw_counts.shape
                # Create an empty array to store the population count for each state (2^n possible combinations)
                num_states = 2 ** ionnumber
                state_counts = np.zeros(num_states, dtype=int)
                # Iterate over each image and calculate the state of the ions
                for k in range(repeat):
                # Convert the brightness/darkness states into an integer (binary number)
                    state = 0
                    for j in range(ionnumber):
                        state |= raw_counts[k, j] << (ionnumber-j-1) # Shift the bit and OR it
                    state_counts[state] += 1
                # Calculate probabilities by dividing the count by the number of repetitions
                probabilities = state_counts / repeat
                self.data.append_raw_data(i, raw_counts)
                return probabilities
        #Globally scan durations
        else:
            scan_ranges = ranges
            def update_duration(duration):
                if checkbeforerun:
                    rightnowionnumber=self.camera.fastcheckionnumber(img_num=3,debug=0)
                    if rightnowionnumber!=self.ion_number:
                        print("detected",rightnowionnumber)
                        print("seted",self.ion_number)
                        raise ValueError("The ion number currentis not equal to the ion number exp set.")
                if isinstance(scan_list,(np.ndarray,list)):
                    for label in scan_list:
                        sequence.set_parameters(label=label, duration=duration, batch_mode=True)
                else:
                    assert isinstance(scan_list,str)
                    sequence.set_parameters(label=scan_list, duration=duration, batch_mode=True)
                sequence.regenerate_sequence()
                if sequence.awg_trigger==True:
                    if _update_sequence != None:
                        _update_sequence(duration)
                    sequence.perform_sequence_awg()
                    self.awg_interface.send_data(sequence.awg_data[0], loop=0)
                    
                #Run experiment
                # np.sum(np.array([[0,1],[2,3]]),axis=0)
                # counts = np.array([sum(np.array([[0,1],[2,3]]))/100])
                # print(counts)
                # print(counts[-1])
                #counts[-1]刚好返回一个概率数组result[ionintex]=probablity_ionindex
                raw_counts = self.run_once(sequence=sequence)
                # Get the shape of the result array
                repeat, ionnumber = raw_counts.shape
                # Create an empty array to store the population count for each state (2^n possible combinations)
                num_states = 2 ** ionnumber
                state_counts = np.zeros(num_states, dtype=int)
                # Iterate over each image and calculate the state of the ions
                for k in range(repeat):
                # Convert the brightness/darkness states into an integer (binary number)
                    state = 0
                    for j in range(ionnumber):
                        state |= raw_counts[k, j] << (ionnumber-j-1) # Shift the bit and OR it
                    state_counts[state] += 1
                # Calculate probabilities by dividing the count by the number of repetitions
                probabilities = state_counts / repeat
                self.data.append_raw_data(duration, raw_counts)
                # print("时间和返回的数据结果是",duration,raw_counts,counts)
                return probabilities
        # sequence updated
        # now please get data for the expermients
        #TODO
        return scan_ranges, update_duration
    
    def time_scan(self, scan_list, ranges, sequence=None, cycle=1, fps=64, plot_flag=True,state_populartion_flag=False, _update_sequence=None,newgui=False,checkbeforerun=False):
        #Update rawdata
        assert sequence != None, "'sequence' cannot be None"
        self.data.title = "TimeScan"
        self.data.reset_raw_data(xtype="Duration")
        #self.data.Sequence = {"Sequence": str(sequence.format_input_data()), "Scan List": scan_list, "Ranges": ranges}
        if self.threshold is None or self.ion_number>10:
            state_populartion_flag=False
        if state_populartion_flag:
            scan_ranges, update_duration = self.time_scan_generator_state_populartion(scan_list, ranges, sequence, _update_sequence,checkbeforerun=checkbeforerun)
        else:
            scan_ranges, update_duration = self.time_scan_generator(scan_list, ranges, sequence, _update_sequence,checkbeforerun=checkbeforerun)
        if newgui:
            run_scan_with_popup(scan_ranges=scan_ranges,update_function=update_duration,title="Frequency scan")
        else:
            scan_generator_pop(func=update_duration, scan_list=scan_ranges, title="Duration (us)", cycle=cycle, plot_flag=plot_flag, dims=self.ion_number,state_populartion_flag=state_populartion_flag)
        self.data.generate_json()
        return 

    def freq_scan_generator(self, scan_list, ranges, sequence, show_list=None, _update_sequence=None):
        n = self.ion_number
        x = np.array([0.5] * n)
        #assert len(scan_list) == len(ranges)
        #Independently scan durations

        if isinstance(ranges[0],tuple):
            scan_ranges=range(len(ranges))
            def update_freq(i):
                sequence.set_parameters(label=scan_list, freq=ranges[i])
                sequence.regenerate_sequence()
                sequence.update_hardwares(scan_list)
                if sequence.awg_trigger==True:
                    if _update_sequence != None:
                        _update_sequence(ranges[i])
                    sequence.perform_sequence_awg()
                    
                    self.awg_interface.send_data(sequence.awg_data[0], loop=0)
                    
                    
                #Run experiment
                
                raw_counts = np.array(self.run_once(sequence=sequence))
                # raw_counts = np.array(self.exp.run_once())
                # print(f"Raw count: {raw_counts}")
                counts = np.array([sum(raw_counts)/self.repeat])
                self.data.append_raw_data(ranges[i][0], raw_counts)
                # print("频率和返回的数据结果是",ranges[i],raw_counts,counts)
                return counts[-1]
            
        elif isinstance(ranges[0],(np.ndarray,list)):
            
            scan_ranges=range(len(ranges[0]))
            def update_freq(i):
                for label, freqs in zip(scan_list, ranges):
                    sequence.set_parameters(label=label, freq=freqs[i], batch_mode=True)
                sequence.regenerate_sequence()
                sequence.update_hardwares(scan_list)
                if sequence.awg_trigger==True:
                    if _update_sequence != None:
                        _update_sequence(freqs[i])
                    sequence.perform_sequence_awg()
                    
                    self.awg_interface.send_data(sequence.awg_data[0], loop=0)
                    
                #Run experiment
                raw_counts = np.array(self.run_once(sequence=sequence))
                # print(f"Raw count: {raw_counts}")
                counts = np.array([sum(raw_counts)/self.repeat])
                self.data.append_raw_data(freqs[0], raw_counts)
                return counts[-1]
            
        #Globally scan durations
        else:
            
            scan_ranges=ranges
            def update_freq(freq):
                if isinstance(scan_list,(np.ndarray,list)):
                    for label in scan_list:
                        sequence.set_parameters(label=label, freq=freq, batch_mode=True)
                else:
                    assert isinstance(scan_list,str)
                    #print(scan_list)
                    #print(sequence.labelled_pulse)
                    sequence.set_parameters(label=scan_list, freq=freq, batch_mode=True)
                sequence.regenerate_sequence()
                sequence.update_hardwares(scan_list)
                if sequence.awg_trigger==True:
                    if _update_sequence != None:
                        _update_sequence(freq)
                    sequence.perform_sequence_awg()
                    
                    self.awg_interface.send_data(sequence.awg_data[0], loop=0)
                    
                #Run experiment
                raw_counts = self.run_once(sequence=sequence)
                # print(f"Raw count: {raw_counts}")
                counts = np.array([sum(raw_counts)/self.repeat]*n)
                self.data.append_raw_data(freq, raw_counts)
                return counts[-1]
        return scan_ranges, update_freq
    
    def freq_scan_generator_state_populartion(self, scan_list, ranges, sequence, _update_sequence=None):
        '''
        scan_list: a list of labels, like '['label1', 'rx1', 'rx2', ...]'
        ranges: frequency list to be scanned
        (Old definition: ranges: a tuple, its length equivalent to scan_list's length)
        '''
        n = self.ion_number
        x = np.array([0.5] * n)
        #assert len(scan_list) == len(ranges)
        #Independently scan durations
        # print(isinstance(ranges[0],(np.ndarray,list)))
        if isinstance(ranges[0],tuple):
            scan_ranges=range(len(ranges))
            def update_freq(i):
                sequence.set_parameters(label=scan_list, freq=ranges[i])
                sequence.regenerate_sequence()
                sequence.update_hardwares(scan_list)
                if sequence.awg_trigger==True:
                    if _update_sequence != None:
                        _update_sequence(ranges[i])
                    sequence.perform_sequence_awg()
                    
                    self.awg_interface.send_data(sequence.awg_data[0], loop=0)
                    
                #Run experiment
                raw_counts = self.run_once(sequence=sequence)
                # Get the shape of the result array
                repeat, ionnumber = raw_counts.shape
                # Create an empty array to store the population count for each state (2^n possible combinations)
                num_states = 2 ** ionnumber
                state_counts = np.zeros(num_states, dtype=int)
                # Iterate over each image and calculate the state of the ions
                for k in range(repeat):
                # Convert the brightness/darkness states into an integer (binary number)
                    state = 0
                    for j in range(ionnumber):
                        state |= raw_counts[k, j] << (ionnumber-j-1) # Shift the bit and OR it
                    state_counts[state] += 1
                # Calculate probabilities by dividing the count by the number of repetitions
                probabilities = state_counts / repeat
                self.data.append_raw_data(i, raw_counts)
                return probabilities
        #Globally scan durations
        elif isinstance(ranges[0],(np.ndarray,list)):
            
            scan_ranges=range(len(ranges[0]))
            def update_freq(i):
                for label, freqs in zip(scan_list, ranges):
                    sequence.set_parameters(label=label, freq=freqs[i], batch_mode=True)
                sequence.regenerate_sequence()
                sequence.update_hardwares(scan_list)
                if sequence.awg_trigger==True:
                    if _update_sequence != None:
                        _update_sequence(freqs[i])
                    sequence.perform_sequence_awg()
                    
                    self.awg_interface.send_data(sequence.awg_data[0], loop=0)
                    
                #Run experiment
                raw_counts = np.array(self.run_once(sequence=sequence))
                # Get the shape of the result array
                repeat, ionnumber = raw_counts.shape
                # Create an empty array to store the population count for each state (2^n possible combinations)
                num_states = 2 ** ionnumber
                state_counts = np.zeros(num_states, dtype=int)
                # Iterate over each image and calculate the state of the ions
                for k in range(repeat):
                # Convert the brightness/darkness states into an integer (binary number)
                    state = 0
                    for j in range(ionnumber):
                        state |= raw_counts[k, j] << (ionnumber-j-1) # Shift the bit and OR it
                    state_counts[state] += 1
                # Calculate probabilities by dividing the count by the number of repetitions
                probabilities = state_counts / repeat
                self.data.append_raw_data(i, raw_counts)
                return probabilities
        else:
            scan_ranges=ranges
            def update_freq(freq):
                if isinstance(scan_list,(np.ndarray,list)):
                    for label in scan_list:
                        sequence.set_parameters(label=label, freq=freq, batch_mode=True)
                else:
                    assert isinstance(scan_list,str)
                    #print(scan_list)
                    #print(sequence.labelled_pulse)
                    sequence.set_parameters(label=scan_list, freq=freq, batch_mode=True)
                sequence.regenerate_sequence()
                sequence.update_hardwares(scan_list)
                if sequence.awg_trigger==True:
                    if _update_sequence != None:
                        _update_sequence(freq)
                    sequence.perform_sequence_awg()
                    
                    self.awg_interface.send_data(sequence.awg_data[0], loop=0)
                    
                #Run experiment
                raw_counts = self.run_once(sequence=sequence)
                # Get the shape of the result array
                repeat, ionnumber = raw_counts.shape
                # Create an empty array to store the population count for each state (2^n possible combinations)
                num_states = 2 ** ionnumber
                state_counts = np.zeros(num_states, dtype=int)
                # Iterate over each image and calculate the state of the ions
                for k in range(repeat):
                # Convert the brightness/darkness states into an integer (binary number)
                    state = 0
                    for j in range(ionnumber):
                        state |= raw_counts[k, j] << (ionnumber-j-1) # Shift the bit and OR it
                    state_counts[state] += 1
                # Calculate probabilities by dividing the count by the number of repetitions
                probabilities = state_counts / repeat
                self.data.append_raw_data(i, raw_counts)
                return probabilities
        return scan_ranges, update_freq
    
    def freq_scan(self, scan_list, ranges, fps=64, sequence=None, cycle=1, show_list=None, plot_flag=True,state_populartion_flag=False, _update_sequence=None):
        if sequence == None:
            sequence = self.last_sequence
        self.data.title = "FreqScan"
        self.data.reset_raw_data(xtype="Frequency")
        if self.threshold is None or self.ion_number>10:
            state_populartion_flag=False
        if state_populartion_flag:
            scan_ranges, update_freq = self.freq_scan_generator_state_populartion(scan_list, ranges, sequence, _update_sequence)
        else:
            scan_ranges, update_freq = self.freq_scan_generator(scan_list, ranges, sequence, show_list, _update_sequence)
        
        scan_generator_pop(func=update_freq, scan_list=scan_ranges, title="Frequency", cycle=cycle, plot_flag=plot_flag, dims=self.ion_number,state_populartion_flag=state_populartion_flag)
        
        self.data.generate_json()
        sequence.reset_hardwares(scan_list)
        return 

    def amp_scan_generator(self, scan_list, ranges, sequence,_update_sequence=None):
        n = self.ion_number
        x = np.array([0.5] * n)
        #assert len(scan_list) == len(ranges)
        #Independently scan durations
        
        if isinstance(ranges[0],tuple):
            scan_ranges=range(len(ranges))
            def update_amp(i):
                sequence.set_parameters(label=scan_list, amp=ranges[i])
                sequence.regenerate_sequence()
                sequence.update_hardwares(scan_list)
                if sequence.awg_trigger==True:
                    if _update_sequence != None:
                        _update_sequence(ranges[i])
                    sequence.perform_sequence_awg()
                    self.awg_interface.send_data(sequence.awg_data[0], loop=0)
                #Run experiment
                raw_counts = np.array(self.run_once(sequence=sequence))
                counts = np.array([sum(raw_counts)/self.repeat])
                self.data.append_raw_data(ranges[i][0], raw_counts)
                return counts[-1]
            
        elif isinstance(ranges[0],(np.ndarray,list)):
            scan_ranges = range(len(ranges[0]))
            def update_amp(i):
                for label, amps in zip(scan_list, ranges):
                    sequence.set_parameters(label=label, amp=amps[i], batch_mode=True)
                
                sequence.update_hardwares(scan_list)
                sequence.regenerate_sequence()
                if sequence.awg_trigger==True:
                    if _update_sequence != None:
                        _update_sequence(amps[i])
                    sequence.perform_sequence_awg()
                    self.awg_interface.send_data(sequence.awg_data[0], loop=0)
                #Run experiment
                raw_counts = self.run_once(sequence=sequence)
                counts = np.array([sum(raw_counts)/self.repeat]*n)
                self.data.append_raw_data(i, raw_counts)
                return counts[-1]
        #Globally scan durations
        else:
            scan_ranges = ranges
            def update_amp(amp):
                if isinstance(scan_list,(np.ndarray,list)):
                    for label in scan_list:
                        sequence.set_parameters(label=label, amp=amp, batch_mode=True)
                else:
                    assert isinstance(scan_list,str)
                    sequence.set_parameters(label=scan_list, amp=amp, batch_mode=True)
                    
                sequence.update_hardwares(scan_list)
                sequence.regenerate_sequence()
                if sequence.awg_trigger==True:
                    if _update_sequence != None:
                        _update_sequence(amp)
                    sequence.perform_sequence_awg()
                    self.awg_interface.send_data(sequence.awg_data[0], loop=0)
                #Run experiment
                raw_counts = self.run_once(sequence=sequence)
                counts = np.array([sum(raw_counts)/self.repeat]*n)
                self.data.append_raw_data(amp, raw_counts)
                return counts[-1]
        # file_name = self.data.generate_json()
        return scan_ranges, update_amp
    
    def amp_scan_generator_state_populartion(self, scan_list, ranges, sequence, _update_sequence=None):
        n = self.ion_number
        x = np.array([0.5] * n)
        #assert len(scan_list) == len(ranges)
        #Independently scan durations
        
        if isinstance(ranges[0],tuple):
            scan_ranges=range(len(ranges))
            def update_amp(i):
                sequence.set_parameters(label=scan_list, amp=ranges[i])
                sequence.regenerate_sequence()
                sequence.update_hardwares(scan_list)
                if sequence.awg_trigger==True:
                    if _update_sequence != None:
                        _update_sequence(ranges[i])
                    sequence.perform_sequence_awg()
                    self.awg_interface.send_data(sequence.awg_data[0], loop=0)
                #Run experiment
                raw_counts = np.array(self.run_once(sequence=sequence))
                # Get the shape of the result array
                repeat, ionnumber = raw_counts.shape
                # Create an empty array to store the population count for each state (2^n possible combinations)
                num_states = 2 ** ionnumber
                state_counts = np.zeros(num_states, dtype=int)
                # Iterate over each image and calculate the state of the ions
                for k in range(repeat):
                # Convert the brightness/darkness states into an integer (binary number)
                    state = 0
                    for j in range(ionnumber):
                        state |= raw_counts[k, j] << (ionnumber-j-1) # Shift the bit and OR it
                    state_counts[state] += 1
                # Calculate probabilities by dividing the count by the number of repetitions
                probabilities = state_counts / repeat
                self.data.append_raw_data(i, raw_counts)
                return probabilities
            
        elif isinstance(ranges[0],(np.ndarray,list)):
            scan_ranges = range(len(ranges[0]))
            def update_amp(i):
                for label, amps in zip(scan_list, ranges):
                    sequence.set_parameters(label=label, amp=amps[i], batch_mode=True)
                
                sequence.update_hardwares(scan_list)
                sequence.regenerate_sequence()
                if sequence.awg_trigger==True:
                    if _update_sequence != None:
                        _update_sequence(amps[i])
                    sequence.perform_sequence_awg()
                    self.awg_interface.send_data(sequence.awg_data[0], loop=0)
                #Run experiment
                raw_counts = self.run_once(sequence=sequence)
                # Get the shape of the result array
                repeat, ionnumber = raw_counts.shape
                # Create an empty array to store the population count for each state (2^n possible combinations)
                num_states = 2 ** ionnumber
                state_counts = np.zeros(num_states, dtype=int)
                # Iterate over each image and calculate the state of the ions
                for k in range(repeat):
                # Convert the brightness/darkness states into an integer (binary number)
                    state = 0
                    for j in range(ionnumber):
                        state |= raw_counts[k, j] << (ionnumber-j-1) # Shift the bit and OR it
                    state_counts[state] += 1
                # Calculate probabilities by dividing the count by the number of repetitions
                probabilities = state_counts / repeat
                self.data.append_raw_data(i, raw_counts)
                return probabilities
        #Globally scan durations
        else:
            scan_ranges = ranges
            def update_amp(amp):
                if isinstance(scan_list,(np.ndarray,list)):
                    for label in scan_list:
                        sequence.set_parameters(label=label, amp=amp, batch_mode=True)
                else:
                    assert isinstance(scan_list,str)
                    sequence.set_parameters(label=scan_list, amp=amp, batch_mode=True)
                    
                sequence.update_hardwares(scan_list)
                sequence.regenerate_sequence()
                if sequence.awg_trigger==True:
                    if _update_sequence != None:
                        _update_sequence(amp)
                    sequence.perform_sequence_awg()
                    self.awg_interface.send_data(sequence.awg_data[0], loop=0)
                #Run experiment
                raw_counts = self.run_once(sequence=sequence)
                # Get the shape of the result array
                repeat, ionnumber = raw_counts.shape
                # Create an empty array to store the population count for each state (2^n possible combinations)
                num_states = 2 ** ionnumber
                state_counts = np.zeros(num_states, dtype=int)
                # Iterate over each image and calculate the state of the ions
                for k in range(repeat):
                # Convert the brightness/darkness states into an integer (binary number)
                    state = 0
                    for j in range(ionnumber):
                        state |= raw_counts[k, j] << (ionnumber-j-1) # Shift the bit and OR it
                    state_counts[state] += 1
                # Calculate probabilities by dividing the count by the number of repetitions
                probabilities = state_counts / repeat
                self.data.append_raw_data(i, raw_counts)
                return probabilities
        # file_name = self.data.generate_json()
        return scan_ranges, update_amp
    
    def amp_scan(self, scan_list, ranges, fps=64, sequence=None, cycle=1,state_populartion_flag=False, _update_sequence=None):
        if sequence == None:
            sequence = self.last_sequence
        self.data.title = "AmpScan"
        self.data.reset_raw_data(xtype="Amp")
        if self.threshold is None or self.ion_number>10:
            state_populartion_flag=False
        if state_populartion_flag:
            scan_ranges, update_amp = self.amp_scan_generator_state_populartion(scan_list, ranges, sequence, _update_sequence)
        else:
            scan_ranges, update_amp = self.amp_scan_generator(scan_list, ranges, sequence, _update_sequence) 
        
        scan_generator_pop(func=update_amp, scan_list=scan_ranges, title="Amp", cycle=cycle, dims=self.ion_number,state_populartion_flag=state_populartion_flag)
        self.data.generate_json()
        return 
    
    def phase_scan_generator(self, scan_list, ranges, sequence, _update_sequence=None):
        n = self.ion_number
        x = np.array([0.5] * n)
        #assert len(scan_list) == len(ranges)
        #Independently scan durations
        
        if isinstance(ranges[0],tuple):
            scan_ranges=range(len(ranges))
            def update_phase(i):
                sequence.set_parameters(label=scan_list, phase=ranges[i])
                sequence.regenerate_sequence()
                sequence.update_hardwares(scan_list)
                if sequence.awg_trigger==True:
                    if _update_sequence != None:
                        _update_sequence(ranges[i])
                    sequence.perform_sequence_awg()
                    self.awg_interface.send_data(sequence.awg_data[0], loop=0)
                #Run experiment
                raw_counts = np.array(self.run_once(sequence=sequence))
                counts = np.array([sum(raw_counts)/self.repeat])
                self.data.append_raw_data(ranges[i][0], raw_counts)
                return counts[-1]
            
        elif isinstance(ranges[0],(np.ndarray,list)):
            scan_ranges = range(len(ranges[0]))
            def update_phase(i):
                for label, phases in zip(scan_list, ranges):
                    sequence.set_parameters(label=label, phase=phases[i], batch_mode=True)
                
                sequence.update_hardwares(scan_list)
                sequence.regenerate_sequence()
                if sequence.awg_trigger==True:
                    if _update_sequence != None:
                        _update_sequence(phases[i])
                    sequence.perform_sequence_awg()
                    self.awg_interface.send_data(sequence.awg_data[0], loop=0)
                #Run experiment
                raw_counts = self.run_once(sequence=sequence)
                counts = np.array([sum(raw_counts)/self.repeat]*n)
                self.data.append_raw_data(i, raw_counts)
                return counts[-1]
        #Globally scan durations
        else:
            scan_ranges = ranges
            def update_phase(phase):
                if isinstance(scan_list,(np.ndarray,list)):
                    for label in scan_list:
                        sequence.set_parameters(label=label, phase=phase, batch_mode=True)
                else:
                    assert isinstance(scan_list,str)
                    sequence.set_parameters(label=scan_list, phase=phase, batch_mode=True)
                    
                sequence.update_hardwares(scan_list)
                sequence.regenerate_sequence()
                if sequence.awg_trigger==True:
                    if _update_sequence != None:
                        _update_sequence(phase)
                    sequence.perform_sequence_awg()
                    self.awg_interface.send_data(sequence.awg_data[0], loop=0)
                #Run experiment
                raw_counts = self.run_once(sequence=sequence)
                counts = np.array([sum(raw_counts)/self.repeat]*n)
                self.data.append_raw_data(phase, raw_counts)
                return counts[-1]
        # file_name = self.data.generate_json()
        return scan_ranges, update_phase
    
    def phase_scan_generator_state_populartion(self, scan_list, ranges, sequence, _update_sequence=None):
        n = self.ion_number
        x = np.array([0.5] * n)
        #assert len(scan_list) == len(ranges)
        #Independently scan durations
        
        if isinstance(ranges[0],tuple):
            scan_ranges=range(len(ranges))
            def update_phase(i):
                sequence.set_parameters(label=scan_list, phase=ranges[i])
                sequence.regenerate_sequence()
                sequence.update_hardwares(scan_list)
                if sequence.awg_trigger==True:
                    if _update_sequence != None:
                        _update_sequence(ranges[i])
                    sequence.perform_sequence_awg()
                    self.awg_interface.send_data(sequence.awg_data[0], loop=0)
                #Run experiment
                raw_counts = np.array(self.run_once(sequence=sequence))
                # Get the shape of the result array
                repeat, ionnumber = raw_counts.shape
                # Create an empty array to store the population count for each state (2^n possible combinations)
                num_states = 2 ** ionnumber
                state_counts = np.zeros(num_states, dtype=int)
                # Iterate over each image and calculate the state of the ions
                for k in range(repeat):
                # Convert the brightness/darkness states into an integer (binary number)
                    state = 0
                    for j in range(ionnumber):
                        state |= raw_counts[k, j] << (ionnumber-j-1) # Shift the bit and OR it
                    state_counts[state] += 1
                # Calculate probabilities by dividing the count by the number of repetitions
                probabilities = state_counts / repeat
                self.data.append_raw_data(i, raw_counts)
                return probabilities
            
        elif isinstance(ranges[0],(np.ndarray,list)):
            scan_ranges = range(len(ranges[0]))
            def update_phase(i):
                for label, phases in zip(scan_list, ranges):
                    sequence.set_parameters(label=label, phase=phases[i], batch_mode=True)
                
                sequence.update_hardwares(scan_list)
                sequence.regenerate_sequence()
                if sequence.awg_trigger==True:
                    if _update_sequence != None:
                        _update_sequence(phases[i])
                    sequence.perform_sequence_awg()
                    self.awg_interface.send_data(sequence.awg_data[0], loop=0)
                #Run experiment
                raw_counts = self.run_once(sequence=sequence)
                # Get the shape of the result array
                repeat, ionnumber = raw_counts.shape
                # Create an empty array to store the population count for each state (2^n possible combinations)
                num_states = 2 ** ionnumber
                state_counts = np.zeros(num_states, dtype=int)
                # Iterate over each image and calculate the state of the ions
                for k in range(repeat):
                # Convert the brightness/darkness states into an integer (binary number)
                    state = 0
                    for j in range(ionnumber):
                        state |= raw_counts[k, j] << (ionnumber-j-1) # Shift the bit and OR it
                    state_counts[state] += 1
                # Calculate probabilities by dividing the count by the number of repetitions
                probabilities = state_counts / repeat
                self.data.append_raw_data(i, raw_counts)
                return probabilities
        #Globally scan durations
        else:
            scan_ranges = ranges
            def update_phase(phase):
                if isinstance(scan_list,(np.ndarray,list)):
                    for label in scan_list:
                        sequence.set_parameters(label=label, phase=phase, batch_mode=True)
                else:
                    assert isinstance(scan_list,str)
                    sequence.set_parameters(label=scan_list, phase=phase, batch_mode=True)
                    
                sequence.update_hardwares(scan_list)
                sequence.regenerate_sequence()
                if sequence.awg_trigger==True:
                    if _update_sequence != None:
                        _update_sequence(phase)
                    sequence.perform_sequence_awg()
                    self.awg_interface.send_data(sequence.awg_data[0], loop=0)
                #Run experiment
                raw_counts = self.run_once(sequence=sequence)
                # Get the shape of the result array
                repeat, ionnumber = raw_counts.shape
                # Create an empty array to store the population count for each state (2^n possible combinations)
                num_states = 2 ** ionnumber
                state_counts = np.zeros(num_states, dtype=int)
                # Iterate over each image and calculate the state of the ions
                for k in range(repeat):
                # Convert the brightness/darkness states into an integer (binary number)
                    state = 0
                    for j in range(ionnumber):
                        state |= raw_counts[k, j] << (ionnumber-j-1) # Shift the bit and OR it
                    state_counts[state] += 1
                # Calculate probabilities by dividing the count by the number of repetitions
                probabilities = state_counts / repeat
                self.data.append_raw_data(i, raw_counts)
                return probabilities
        # file_name = self.data.generate_json()
        return scan_ranges, update_phase
    
    def phase_scan(self, scan_list, ranges, fps=64, sequence=None, cycle=1,state_populartion_flag=False, _update_sequence=None):
        if sequence == None:
            sequence = self.last_sequence
        self.data.title = "PaseScan"
        self.data.reset_raw_data(xtype="Phase")
        if self.threshold is None or self.ion_number>10:
            state_populartion_flag=False
        if state_populartion_flag:
            scan_ranges, update_phase = self.phase_scan_generator_state_populartion(scan_list, ranges, sequence, _update_sequence)
        else:
            scan_ranges, update_phase = self.phase_scan_generator(scan_list, ranges, sequence, _update_sequence) 
        
        scan_generator_pop(func=update_phase, scan_list=scan_ranges, title="Phase", cycle=cycle, dims=self.ion_number,state_populartion_flag=state_populartion_flag)
        self.data.generate_json()
        return 
    
    def run_with_awg(self, sequence=None):
        #Update rawdata
        assert sequence != None, "'sequence' cannot be None"
        #self.data.Sequence = {"Sequence": str(sequence.format_input_data()), "Scan List": scan_list, "Ranges": ranges}
        sequence.regenerate_sequence()
        if sequence.awg_trigger==True:
            sequence.perform_sequence_awg()
            self.awg_interface.send_data(sequence.awg_data[0], loop=0)
        raw_counts = self.run_once(sequence=sequence)
        return raw_counts
    
    
    def print_sequence(self, size=(10, 4), sequence=None,  remap_time = False):
        """
        将实验序列绘制成图像

        Parameters
        ----------
        size : tuple, optional
            图像大小, by default (10, 4)
        sequence : Sequence, optional
            实验序列, by default None
        remap_time : bool, optional
            是否将时间轴进行重映射, by default False
        """
        if sequence == None:
            sequence = self.last_sequence
        #figure = plt.figure(figsize=size)
        plot_sequence(sequence.gate_sequence, remap_time=remap_time, auto_size = False, given_size=size)
        plt.tight_layout()
        plt.show()
        pass

    def save_info_to_database():
        """
        #TODO
        """
        pass

# hardware related functions

    def send_awg_data(self, sequence=None, print_flag=False):
        if sequence == None:
            sequence = self.last_sequence
        sequence.perform_sequence_awg()
        if Global_Setting.get_debug_mode() == True:
            fig, axs= plt.subplots(nrows = 1, ncols = self.ion_number, figsize=(27, 6))
            i=0
            for ax in axs:
                ax.plot(sequence.awg_data[i])
                i += 1
            plt.show()
        else:
            if sequence.awg_trigger == True:
                self.awg_interface.send_data(sequence.awg_data, print_flag=print_flag)
            else:
                print("Caution! You are trying to send awg data while no awg interface defined. Check the ip address?")

    def run_once(self, sequence=None):
        return self.run_once_pmt(sequence)

    def run_once_pmt(self, sequence=None):
        if sequence == None:
            sequence = self.last_sequence
        repeat = self.repeat
        threshold = [self.threshold,self.state_flag]
        pulses = sequence.perform_sequence()
        pulses = hardwarecomm.pre_binary(pulses, self.clock_period)
        packets = [hardwarecomm.packets_generator(pulses, self.ext_trig, repeat), int(repeat*self.bytes_returned)]

        # 如果处于实验模式下，将编码后的数据通过串口发送给FPGA，并读取FPGA的返回结果至result
        if Global_Setting.get_debug_mode() == False:
            result = hardwarecomm.seq_send(self.ser, packets, threshold)
        # 如果处于debug模式下，则是直接生成相应长度的随机序列，存入result中
        else:
            result = np.random.rand(self.ion_number*self.repeat) * 20
            time.sleep(0.005)
        return split_array(np.array(result), self.ion_number)  
        
        
    
    def run_twice_pmt(self, sequence=None):
        
        if sequence == None:
            sequence = self.last_sequence
        repeat = self.repeat
        threshold = [self.threshold,self.state_flag]
        pulses = sequence.perform_sequence()
        pulses = hardwarecomm.pre_binary(pulses, self.clock_period)
        packets = [hardwarecomm.packets_generator(pulses, self.ext_trig, repeat), int(repeat*self.bytes_returned)]

        # 如果处于实验模式下，将编码后的数据通过串口发送给FPGA，并读取FPGA的返回结果至result
        if Global_Setting.get_debug_mode() == False:
            result = hardwarecomm.seq_send_twice(self.ser, packets, threshold)
        # 如果处于debug模式下，则是直接生成相应长度的随机序列，存入result中
        else:
            result = np.random.rand(self.ion_number*self.repeat) * 20
            time.sleep(0.005)
        return result
    
    def EMCCD_backgroundimage_generate(self, sequence=None, ext_trigger=1):
        if sequence == None:
            sequence = self.last_sequence
        
        repeat  = self.repeat + 10 # run addtional 10 times to make sure the EMCCD acquisition is finished
        
        # 图片的数量等于repeat数
        img_num = self.repeat
        kinetic_cycle_time = self.camera.data_mode_init(img_num=img_num, exposure_time=self.camera_parameters["exposure_time"], h_redundancy=self.camera_parameters["h_redundancy"], v_redundancy=self.camera_parameters["v_redundancy"], hbin=self.camera_parameters["hbin"], vbin=self.camera_parameters["vbin"], ext_trigger=ext_trigger, output_flag=False)
        threshold_time_emccd = kinetic_cycle_time*10**6+500
        pulses = sequence.perform_sequence()
        if sequence.total_duration < threshold_time_emccd:
            pulses[0][1] += threshold_time_emccd-sequence.total_duration           # if the duration of sequence smalller than kinetic cycle time, we compensate the gap by making the first chapter longer.
        pulses = hardwarecomm.pre_binary(pulses, self.clock_period)
        packets = [hardwarecomm.packets_generator(pulses, self.ext_trig, repeat), int(repeat*self.bytes_returned)]

        # 如果处于实验模式下，将编码后的数据通过串口发送给FPGA，并读取FPGA的返回结果至result
        if Global_Setting.get_debug_mode() == False:
            self.camera.camera.start_acquisition()
            time.sleep(0.001)
            hardwarecomm.seq_send_camera(self.ser, packets)
            time.sleep(0.001)
            images = self.camera.read()
            image_averaged = sum(images) / len(images)
        else:
            image_averaged=None
        self.camera.barkgroundimage=image_averaged
        return None

    
    
    def calibrate_camera(self, sequence=None, ext_trigger=1,backgroundsubstraction=1):
        #this funciton find ion count while don't calibrate ion position and size, use camera.calibrate to find ion position
        #remember change Detection to Detection_Camera
        if sequence == None:
            sequence = self.last_sequence
        repeat  = self.repeat+10 # run addtional 10 times to make sure the EMCCD acquisition is finished
        img_num = self.repeat
        kinetic_cycle_time = self.camera.data_mode_init(img_num=img_num, exposure_time=self.camera_parameters["exposure_time"], h_redundancy=self.camera_parameters["h_redundancy"], v_redundancy=self.camera_parameters["v_redundancy"], hbin=self.camera_parameters["hbin"], vbin=self.camera_parameters["vbin"], ext_trigger=ext_trigger)
        threshold_time_emccd = kinetic_cycle_time*10**6+500
        threshold = [self.threshold,self.state_flag]
        # print("calibrate_camera,threshold",threshold)
        pulses = sequence.perform_sequence()
        
        if sequence.total_duration < threshold_time_emccd:
            pulses[0][1] += threshold_time_emccd-sequence.total_duration # if the duration of sequence smalller than kinetic cycle time, we compensate the gap by making the first chapter longer.
            print(pulses[0][1])
            
            
        pulses = hardwarecomm.pre_binary(pulses, self.clock_period)
        packets = [hardwarecomm.packets_generator(pulses, self.ext_trig, repeat), int(repeat*self.bytes_returned)]

        # 如果处于实验模式下，将编码后的数据通过串口发送给FPGA，并读取FPGA的返回结果至result
        if Global_Setting.get_debug_mode() == False:
            self.camera.camera.start_acquisition()
            time.sleep(0.001)
            hardwarecomm.seq_send(self.ser, packets, threshold)
            time.sleep(0.1)
            images = self.camera.read()
        image_averaged = sum(images) / len(images)
        self.camera.calibrate_parameter = self.camera.calibrate_images_fit(self.camera.ion_position_list_data_mode, image_averaged,backgroundsubstraction=backgroundsubstraction)# if you need to wait for camera_detction.measure_and_fit to finish
        return self.camera.calibrate_parameter 

    
    def run_once_camera(self, sequence=None, ext_trigger=1,debug=0,backgroundsubstraction=1):
        if sequence == None:
            sequence = self.last_sequence
        repeat  = self.repeat + 10 # run addtional 10 times to make sure the EMCCD acquisition is finished
        
        # 图片的数量等于repeat数
        img_num = self.repeat
        kinetic_cycle_time = self.camera.data_mode_init(img_num=img_num, exposure_time=self.camera_parameters["exposure_time"], h_redundancy=self.camera_parameters["h_redundancy"], v_redundancy=self.camera_parameters["v_redundancy"], hbin=self.camera_parameters["hbin"], vbin=self.camera_parameters["vbin"], ext_trigger=ext_trigger, output_flag=False)
        threshold_time_emccd = kinetic_cycle_time*10**6+500
        threshold = [self.threshold,self.state_flag]
        # print("run_once_camera,threshold",threshold)
        pulses = sequence.perform_sequence()
        
        if sequence.total_duration < threshold_time_emccd:
            pulses[0][1] += threshold_time_emccd-sequence.total_duration           # if the duration of sequence smalller than kinetic cycle time, we compensate the gap by making the first chapter longer.
            
        pulses = hardwarecomm.pre_binary(pulses, self.clock_period)
        packets = [hardwarecomm.packets_generator(pulses, self.ext_trig, repeat), int(repeat*self.bytes_returned)]

        # 如果处于实验模式下，将编码后的数据通过串口发送给FPGA，并读取FPGA的返回结果至result
        if Global_Setting.get_debug_mode() == False:
            self.camera.camera.start_acquisition()
            time.sleep(0.001)
            hardwarecomm.seq_send_camera(self.ser, packets)
            time.sleep(0.001)
            result = np.array(self.camera.read_and_fit(debug=debug,backgroundsubstraction=backgroundsubstraction,threshold=self.threshold))
            #数据处理，把一维数组变成二维数组，每个图片是一个一维数组。
            result = split_array(result, len(self.camera.ion_position_list_data_mode))  
            # #把二维数组倒置，这样每个离子就是一个一维数组，可以直接使用。
            # result = result.T

        else:
            result = np.random.rand(self.ion_number*repeat) * 20
            time.sleep(0.005)

        if self.threshold is not None and self.state_flag:   ### 这部分是在处理如果设置了阈值，那么将做判断，转化成fidelity
            assert len(self.threshold)==self.ion_number, "The length of threshold should be same with the number of ions."
            result_temp = []
            for shot in result:
                shot = shot >= np.array(self.threshold)
                shot = shot.flatten().astype(int)
                result_temp = result_temp + [shot]
            result = np.array(result_temp)         

        return result

    def update_awg(self, sequence=None):
        if sequence == None:
            sequence = self.last_sequence
        awg_data_temp = sequence.generator_awg_waveform
        self.awg.update_waveform(awg_data_temp)
        return   


class Sequence:

    def __init__(self, ion_number = 5, chapter_dict = None, sampling_rate = 625):
        """
        初始化一个Sequence对象，用于描述一个实验序列

        Parameters
        ----------
        ion_number : int, optional
            _description_, by default 5
        chapter_dict : _type_, optional
            _description_, by default None
        sampling_rate : int, optional
            _description_, by default 625
        """
        self.awg_sampling_rate = 625 #unit: MHz /default: 625 MHz
        self.awg_trigger = False
        self.awg_trigger_time = -1 #awg_trigger打开的时间
        self.current_time = -1 #表示上一个门结束的时间
        self.input_list = []
        self.set_para_flag = False
        self.gate_sequence = []
        self.ion_number = ion_number # total ions
        self.time_stamp = [0 for i in range(self.ion_number)]
        #self.gate_list = []
        self.gate_list_per_ion = [[] for i in range(ion_number)]
        self.labelled_pulse = {}
        self.awg_data = None
        self.real_chapter_dict = chapter_dict


    def set_parameters(self, label = None, duration = None, amp = None, freq = None, phase = None, para_table = None, batch_mode = True, regenerate_sequence = True):
        """
        
        #请注意，如果想对一组pulse赋予相同的值，应设置batch_mode = True（默认值是True）。这时程序会无条件将相同的参数赋值给每一个pulse
        #batch_mode == False的情况主要是用于同时对多个相同标签的gate的参数赋值
        
        #例如：set_parameters(label='rx0', duration=100, amp = (10,20))或set_parameters(label='rx0', duration=100, amp = (10,20), batch_mode = True)
        #表示批量赋值：将所有label为'rx0'的gate的duration设为100，amp设为(10,20)。即每个'rx0'的gate有两个sin分量，这两个sin分量的amp是10和20
        
        # batch_mode == False的情形：
            #若是set_parameters(label='rx0', duration=100, batch_mode = False)
            #且label为'rx0'的gate数目大于1时，程序会报错
        
            #或者set_parameters(label='raman', amp=(300,200), batch_mode = False)
            #此时cion会要求本序列中有两个标签，软件认为用户是想把amp=300和amp=200分别赋值给这两个raman gate，而不是看成一个raman的两个sin分量的amplitude
        
        ########################
        ########################
        #list用于描述分段函数（即一个gate分为多个时间段）
        #如果想同时修改多个pulse的某个参数，并且对每个pulse赋予不同的值，请一定使用tuple类型！！使用list会产生歧义。
        ########################
        #例如：有三个pulse的label都是'Rx'时，调用如下函数
        # amp = [(amp1, amp2, amp3) for i in range(12)]
        #set_parameters(label = 'Rx', duration = duration, amp = [amp1, amp2, amp3], freq = (freq1,freq2,freq3))
        #这里，程序会报错，因为并没有设置batch_mode = True，程序发现amp(虽然是list)是赋值给单个pulse的，无法分配给三个pulse
        #而如下参数形式是允许的：
        #set_parameters(label = 'Rx', duration = duration, freq = (freq1,None,freq3), phase = (phase0, [phase1,phase2,phase3], phase4))
        #这表明第二个pulse的phase参数确实是一个list，并且被修改为[phase1,phase2,phase3]，list用于处理分段函数的情况
        #而phase1,phase2,phase3本身也可以是tuple类型，用于处理每一段函数内部有多个sine分量相加的情况，具体请看相关示例
        ########################
        #而使用tuple类型分开赋值时，请确保tuple的元素个数与需要修改的pulse个数相等
        #不想修改的直接传None——如上面freq = (freq1,None,freq3)表示第2个pulse的freq参数不做修改

        Parameters
        ----------
        label : _type_, optional
            _description_, by default None
        duration : _type_, optional
            _description_, by default None
        amp : _type_, optional
            _description_, by default None
        freq : _type_, optional
            _description_, by default None
        phase : _type_, optional
            _description_, by default None
        para_table : _type_, optional
            _description_, by default None
        batch_mode : bool, optional
            _description_, by default True
        regenerate_sequence : bool, optional
            _description_, by default True

        Raises
        ------
        e
            _description_
        """
        assert label != None

        if self.labelled_pulse.get(label) == None:
            assert False
        self.set_para_flag = True

        gate_category = -1
        for gate in self.labelled_pulse[label]:
            # print(type(gate),isinstance(gate, BaseGate))
            if isinstance(gate, BaseGate):
                #用于单离子的BaseGate
                assert gate_category in {-1,1}
                gate_category = 1
            else:
                #专用于多离子纠缠的AdvanceGate
                assert gate_category in {-1,2}
                gate_category = 2

        # print("gate_category",gate_category)
        if gate_category == 1:
            all_paras = [duration, amp, freq, phase]
            num = len(self.labelled_pulse[label])
            para_names = ['duration', 'amp', 'freq', 'phase']
            # print(all_paras,num,para_names)
            if num > 1:
                if batch_mode == False:
                    for idx,para in enumerate(all_paras):
                        if para != None:
                            try:
                                assert type(para) == tuple
                                assert len(para) == num
                            except Exception as e:
                                print("Error during setting parameters! The shape of \'{}\' does not match the number of pulses {}".format(para_names[i], num))
                                raise e

            for i in range(4):
                para = all_paras[i]
                # print("i,para",i,para)
                if para != None:
                    if batch_mode == False:
                        if type(para) != tuple:
                            para = [para for j in range(num)]
                        # print('i, para: ',i, para)
                        # print(num)
                        assert len(para) == num
                        for j in range(num):
                            if para[j] != None:
                                if i == 0:
                                    self.labelled_pulse[label][j].duration = copy.copy(para[j])
                                if i == 1:
                                    self.labelled_pulse[label][j].amp = copy.copy(para[j])
                                if i == 2:
                                    self.labelled_pulse[label][j].freq = copy.copy(para[j])
                                if i == 3:
                                    self.labelled_pulse[label][j].phase = copy.copy(para[j])
                    else: # batch_mode : allocate identical parameters to all pulses
                        for j in range(num):
                                if i == 0:
                                    self.labelled_pulse[label][j].duration = copy.copy(para)
                                if i == 1:
                                    self.labelled_pulse[label][j].amp = copy.copy(para)
                                if i == 2:
                                    self.labelled_pulse[label][j].freq = copy.copy(para)
                                if i == 3:
                                    self.labelled_pulse[label][j].phase = copy.copy(para)
        else:
            assert para_table != None
            if batch_mode == False:
                if type(para_table) !=  tuple:
                    para_table = [para_table]
                assert len(para_table) == len(self.labelled_pulse[label])
                for idx, single_dict in para_table:
                    if single_dict != None:
                        self.labelled_pulse[label][idx].set_parameter_table( copy.copy(single_dict) )
            else:
                for gate in self.labelled_pulse[label]:
                    gate.set_parameter_table( copy.copy(para_table) )
        '''
        if duration != None:
            if type(duration) != tuple:
                duration = [duration for i in range(num)]
            assert len(duration) == num
            for i in range(num):
                if duration[i] != None:
                    self.labelled_pulse[label][i].duration = duration[i]
        '''
        # print("self.labelled_pulse[AWG1][0],duration,pluse,amp,freq,phase",self.labelled_pulse[label][0].duration,self.labelled_pulse[label][0].amp,self.labelled_pulse[label][0].freq,self.labelled_pulse[label][0].phase)
        if regenerate_sequence == True:
            self.regenerate_sequence()
            self.set_para_flag = False
        
    #continuous_seq = [RSB(paras,10.0),opitical_pumping(102,13),...]

    #seq.add_sequence(EIT(100...),continuous_seq,optical_pumping)

    def update_hardwares(self, label=None):
        if isinstance(label, str):
            try:
                self.labelled_pulse[label][0].update_hardware()
            except AssertionError:
                pass
        else:
            for i in label:
                try:
                    self.labelled_pulse[i][0].update_hardware()
                except AssertionError:
                    pass

    def format_input_data(self):
        input_list = self.input_list
        output = []
        for gate in input_list:
            output += [[gate.label, gate.duration]]
        return output

    def reset_hardwares(self, label=None):
        if isinstance(label, str):
            self.labelled_pulse[label][0].reset_hardware()
        else:
            for i in label:
                self.labelled_pulse[i][0].reset_hardware()

    def __add_gate_composite(self, gate):
        chapter_list = self.real_chapter_dict[gate.pulse_type]
        #print(chapter_list)
        #chapter_list = self.real_chapter_dict[(gate.pulse_type. gate.ion_index)]
        ion_index = gate.ion_index
        for chapter in chapter_list:
            if isinstance(chapter, str):
                gate.chapter_data = chapter
                self.__add_gate_non_composite(gate)
            else:
                t = chapter[1]
                temp_gate = BaseGate(duration=t, awg_flag=False)
                temp_gate.chapter_data = chapter[0]
                #print(temp_gate.pulse_type)
                self.__add_gate_non_composite( temp_gate.on(ion_index) )

    def __add_gate_non_composite(self, gate):
        #if isinstance(gate, tuple):
        #    print(gate)
        
        if isinstance(gate, BaseGate):
            ion = gate.ion_index

            assert ion != None
            gate.duration = np.round(np.round(gate.duration / 0.04) * 0.04,2)
            if gate.duration < 0.04:
                gate.duration = 0.04
            tn = gate.duration
            tl = gate.latency
            if gate.label != None:
                if self.labelled_pulse.get(gate.label) != None:
                    self.labelled_pulse[gate.label].append(gate)
                else:
                    self.labelled_pulse[gate.label] = [gate]

            if isinstance(ion, int):
                self.gate_sequence.append((ion, self.time_stamp[ion]+tl, tn, gate.pulse_type, gate))
                self.time_stamp[ion] += tn + tl
                self.current_time = self.time_stamp[ion]
                if gate.awg_flag == True:
                    assert self.awg_trigger == True
                    self.gate_list_per_ion[ion].append((self.time_stamp[ion]-tn, tn, gate))
            else:
                tmax = -1
                if ion == empty:
                    ion = all
                for single_ion in ion:
                    tmax = max(tmax, self.time_stamp[single_ion])
                self.gate_sequence.append((ion, tmax+tl, tn, gate.pulse_type, gate))
                for single_ion in ion:
                    self.time_stamp[single_ion] = tmax + tn +tl
                    if gate.awg_flag == True:
                        assert self.awg_trigger == True
                        self.gate_list_per_ion[single_ion].append((self.time_stamp[single_ion]-tn, tn, gate))
                self.current_time = tmax + tn +tl
            gate.start_time = self.gate_sequence[-1][1]
            gate.end_time = self.gate_sequence[-1][1] + self.gate_sequence[-1][2]
        else:
            assert isinstance(gate, AdvanceGate)
            assert gate.ion_index != None
            para_dict = gate.para_table
            tl = gate.latency
            tmax = -1
            time_segment_number = para_dict['segment_number']
            #gate duration即为分段函数最后一段的结束时间
            tn = para_dict['time_intervals'][time_segment_number-1][1]
            # print(gate.ion_index, gate.pulse_type)
            for single_ion in iter_int_or_tuple(gate.ion_index):
                assert para_dict['data_per_ion'][single_ion] != None
                tmax = max(tmax, self.time_stamp[single_ion])
            #gate.ion_index里必须有离子，即必须on在至少一个离子上tmax才不等于-1 
            try:
                assert tmax != -1
            except AssertionError as e:
                print('Error! This {} gate is not performed on any ions.'.format(gate.pulse_type))
                raise e
            
            self.gate_sequence.append((gate.ion_index, tmax+tl, tn, gate.pulse_type, gate))
            
            for single_ion in iter_int_or_tuple(gate.ion_index):
                self.time_stamp[single_ion] = tmax + tn + tl
                self.gate_list_per_ion[single_ion].append((self.time_stamp[single_ion] - tn, tn, gate))
            

    def __add_gate(self, gate):
        composite_flag = False
        if gate.pulse_type == None:
            print(gate)
        gate_chapter = self.real_chapter_dict[gate.pulse_type]
        if gate.awg_flag == False and len(gate_chapter) > 1:
            composite_flag = True
        if composite_flag:
            self.__add_gate_composite(gate)
        else:
            #if gate.awg_flag == False:
                #gate.chapter_data = self.real_chapter_dict[gate.pulse_type][0]
            
            # 针对单离子门或多离子门，将gate chapter的第9-16位进行修改，在对应例子位置上赋"1"
            # PS: 对于16个离子的系统，实际应该是修改9-24位，请先找回隐藏的8bit
            gate.chapter_data = list(gate_chapter[0])
            for ion in iter_int_or_tuple(gate.ion_index):
                gate.chapter_data[ion+8] = '1'
            gate.chapter_data = ''.join(gate.chapter_data)
            
            self.__add_gate_non_composite(gate)


    def add_gates(self, *args):
        self.input_list.extend(list(args))
        self.inner_add_gates(args)

    def inner_add_gates(self, args):
        gate_num = len(args)
        # print("args",args)
        for item in args:
            # print("inner_add_gates item", item)
            # print("iter_gate_or_tuple_or_list(item)",iter_gate_or_tuple_or_list(item))
            for pulse in iter_gate_or_tuple_or_list(item):
                # print("pulse",pulse)
                if isinstance(pulse, BaseGate) or isinstance(pulse, AdvanceGate):
                    self.__add_gate(pulse)
                elif isinstance(pulse, tuple):
                    tmax = -1
                    if pulse[0] == 'sync':
                        for single_ion in pulse[1]:
                            tmax = max(tmax, self.time_stamp[single_ion])
                        for single_ion in pulse[1]:
                            self.time_stamp[single_ion] = tmax
                        self.current_time = tmax
                    elif pulse[0] == 'awg_trigger':
                        assert self.awg_trigger == False
                        self.awg_trigger = True
                        self.awg_trigger_time = self.current_time + pulse[1]
                    elif pulse[0] == 'end_branch':
                        for single_ion in all:
                            tmax = max(tmax, self.time_stamp[single_ion])
                        for single_ion in all:
                            self.time_stamp[single_ion] = tmax
                        self.gate_sequence.append((all, tmax, 1, 'end_branch', EndBranch(tmax)))
                    elif pulse[0] == 'end':
                        for single_ion in all:
                            tmax = max(tmax, self.time_stamp[single_ion])
                        for single_ion in all:
                            self.time_stamp[single_ion] = tmax
                        self.gate_sequence.append((all, tmax, 1, 'end', End(tmax)))
                    else:
                        print('Error in pulse sequence, exception type not permitted')
                        raise Exception()
                    #self.gate_list.append()
                else:
                    pass

    def set_sequence(self, *args):
        self.clear(make_sure = True)
        # print("set_sequence",args)
        self.input_list = list(args)
        self.inner_add_gates(args)

    def print_info(self):
        tmax = 0
        for pulse in self.gate_sequence:
            tmax = max(tmax, pulse[1]+pulse[2])
        print('----sequence info----')
        print('total pulses: {}'.format(len(self.gate_sequence)))
        print('sequence time: {} microseconds'.format(round(tmax,4)))
        #print(self.gate_sequence)
        print('---------------------')

    def show_sequence(self):
        #draw a graph demonstrating the layout of sequences like Cirq does
        pass

    #def convert_sequence(self):
        # convert sequence to xml format

    def reformat_sequence(self):
        '''
        start_point : Dict; each element is a list of 3-tuples: (sequence_index, gate_object, ion_index)
        end_point : Dict; similar to start_point
        branch_point: Dict;
        '''
        seq = self.gate_sequence
        time_index = set()

        for pulse in seq:
            time_index.add(pulse[1])
            time_index.add(pulse[1]+pulse[2])
        time_index = list(time_index)
        time_index.sort()
        #print(time_index)
        start_point = {}
        end_point = {}
        label_time = {}
        end_branch_positions = set()
        for idx,pulse_item in enumerate(seq):
            lt = pulse_item[1]
            rt = pulse_item[1]+pulse_item[2]
            pulse = pulse_item[4]
            if start_point.get(lt) != None:
                start_point[lt].append((idx,pulse_item[3],pulse_item[0], pulse_item[4]))
            else:
                start_point[lt] = [(idx,pulse_item[3],pulse_item[0], pulse_item[4])]
            if end_point.get(rt) != None:
                end_point[rt].append((idx,pulse_item[3],pulse_item[0], pulse_item[4]))
            else:
                end_point[rt] = [(idx,pulse_item[3],pulse_item[0], pulse_item[4])]
            if pulse.label != None:
                label_time[pulse.label] = lt
            if isinstance(pulse, End) or isinstance(pulse, EndBranch):
                end_branch_positions.add(lt)
        branch_point = {}
        for idx,pulse_item in enumerate(seq):
            lt = pulse_item[1]
            rt = pulse_item[1]+pulse_item[2]
            pulse = pulse_item[4]
            if pulse.branch_dict != None:
                mid_idx = -1
                for idx,segment in enumerate(self.real_chapter_dict[pulse.pulse_type]):
                    if isinstance(segment, str):
                        mid_idx = idx
                cycles = pulse.branch_dict['cycles']
                tf_set = {}
                if pulse.branch_dict['tlabel'] != None:
                    tlabel = pulse.branch_dict['tlabel']
                    tpoint = label_time[tlabel]
                    tf_set['t'] = tpoint
                if pulse.branch_dict['flabel'] != None:
                    flabel = pulse.branch_dict['flabel']
                    fpoint = label_time[flabel]
                    tf_set['f'] = fpoint
                branch_point[lt] = (pulse.branch_dict['ion'], tf_set, cycles) 
        #print(start_point, end_point)
        return time_index, start_point, end_point, branch_point, end_branch_positions

    def get_chapter_data(self, pulse_type, ion_index):
        '''
        pulse_type is a string, such as 'EIT', 'optical_pumping', 'RSB' ...
        ion_index is an int or tuple, such as: 0,2,3 or (3), (0,2), (1,3,4)
        '''
        #TODO
        #this function is left for modification to get real chapter data according to the experiment configurations.
        return self.real_chapter_dict[pulse_type]

    def callable_test(self,func,t):
        if callable(func):
            result = func(t)
        else:
            result = func
        return result

    def func_generator(self,args):
        if isinstance(args['amp'],tuple):
            # print(args['amp'], args['freq'], args['phase'])
            def func(t):
                func_list_temp = [self.callable_test(args['amp'][i], t)*np.sin(self.callable_test(2*np.pi*args['freq'][i], t)*t+self.callable_test(args['phase'][i], t)) for i in range(len(args['amp']))]
                return sum(func_list_temp)
           # for i in range(len(args['amp'])):
           #     print(args['amp'][i])
           #     def func_temp(t):
           #         return self.callable_test(args['amp'][i], t)*np.sin(self.callable_test(2*np.pi*args['freq'][i], t)*t+self.callable_test(args['phase'][i], t))
           #     func_list_temp = func_list_temp + [func_temp]
           # def func(t):
           #     result = 0
           #     for i in range(len(func_list_temp)):
           #         result = result + func_list_temp[i](t)
           #     return result
            
        else:
            def func(t):
                try:
                    return self.callable_test(args['amp'], t)*np.sin(self.callable_test(2*np.pi*args['freq'], t)*t+self.callable_test(args['phase'], t))
                except TypeError:
                    print(args['amp'], args['freq'], args['phase'])
                    raise TypeError
                    #pass

        return func

    def func_list_generator(self):
        #对于gate不分段的情况：
        #input is self.parameter_list = [{'amp':(amp_11,amp_12,...),'freq': (freq_11,freq_12,...), 'phase': (phase_11, phase_12,...)}, {'amp': amp_2, 'freq': freq_2, 'phase': phase_2}...]
        #其中同一个tuple里面的amp_11, amp_12表示不同的sin分量
        #
        #如果gate本身是分段的，这里需要做一下处理：
        #input is self.parameter_list = [{'amp':[amp_seg1, amp_seg2, ...],'freq': [freq_seg1,freq_seg2,...], 'phase': [phase_seg1, ...]},...]
        #其中amp_seg1又是(amp_11,amp_12,...)的形式, 对应多个sin分量
        func_list = []
        t_list = []
        relative_list = []
        #print(self.gate_list_per_ion[0])
        for ion_id,gate_list in enumerate(self.gate_list_per_ion):
            # gate_list_per_ion存下每个离子对应的awg_gate的list，list中每个元素都是一个三元组(start_time, duration, gate_object)
            # 其中gate是的一个BaseGate对象或AdvanceGate对象
            func_list_single_ion = []
            t_list_single_ion = []
            relative_list_single_ion = []
            #print(func_list_single_ion)

            for gate_tuple in gate_list:
                # print("这是产生函数时的，gate_tuple[2]",gate_tuple[2].parameter_list())
                # (start_time, duration, gate_object)
                # 用list表示分段函数

                if  isinstance(gate_tuple[2], BaseGate):
                    if (gate_tuple[2].awg_flag == True):
                        if (gate_tuple[2].wave_func != None):
                            for wave_func in iter_gate_or_tuple_or_list(gate_tuple[2].wave_func):
                                try:
                                    assert callable(wave_func)
                                    assert wave_func.__code__.co_argcount == 1
                                except AssertionError as e:
                                    print('Error in check wave function on ion {} .'.format(gate_tuple[2].ion_index))
                                    raise e
                            if isinstance(gate_tuple[2].wave_func, list):
                                accumulate_duration = 0
                                for idx,wave_func in enumerate(gate_tuple[2].wave_func):
                                    func_list_single_ion = func_list_single_ion + [wave_func]
                                    dur_i = gate_tuple[2].segment_duration[idx]
                                    new_accumulate_duration = round_004(accumulate_duration + dur_i)
                                    if idx == len(gate_tuple[2].wave_func)-1:
                                        new_accumulate_duration = gate_tuple[1]
                                    t_list_single_ion.append((gate_tuple[0]+accumulate_duration, gate_tuple[0]+new_accumulate_duration))
                                    accumulate_duration = new_accumulate_duration
                                assert accumulate_duration == gate_tuple[1]
                            else:
                                func_list_single_ion = func_list_single_ion + [gate_tuple[2].wave_func]
                                t_list_single_ion.append((gate_tuple[0], gate_tuple[0]+gate_tuple[1]))    
                        elif type(gate_tuple[2].amp) == list and len(gate_tuple[2].amp) == gate_tuple[2].segment_number:
                            for idx in range(len(gate_tuple[2].time_intervals)):
                                args = gate_tuple[2].parameter_list(idx)
                                try:
                                    assert args['amp'] != None
                                    assert args['freq'] != None
                                    assert args['phase'] != None
                                except AssertionError as e:
                                    print('Error in generating function, some parameters of a gate on ion {} are None.'.format(gate_tuple[2].ion_index))
                                    raise e
                                lt, rt = gate_tuple[2].time_intervals[idx]
                                if rt <= gate_tuple[2].duration:
                                    t_list_single_ion.append((gate_tuple[0]+lt, gate_tuple[0]+rt))
                                    func_list_single_ion = func_list_single_ion + [self.func_generator(gate_tuple[2].parameter_list(idx))]
                                elif lt <= gate_tuple[2].duration:
                                    t_list_single_ion.append((gate_tuple[0]+lt, gate_tuple[0]+gate_tuple[2].duration))
                                    func_list_single_ion = func_list_single_ion + [self.func_generator(gate_tuple[2].parameter_list(idx))]
                        else:
                            args = gate_tuple[2].parameter_list()
                            try:
                                assert args['amp'] != None
                                assert args['freq'] != None
                                assert args['phase'] != None
                            except AssertionError as e:
                                print('Error in generating function, some parameters of a gate on ion {} are None.'.format(gate_tuple[2].ion_index))
                                raise e
                            #print('ion {}:'.format(idx) , gate_tuple[2].parameter_list())
                            #print(gate_tuple[2].parameter_list())
                            func_list_single_ion = func_list_single_ion + [self.func_generator(gate_tuple[2].parameter_list())]
                            t_list_single_ion.append((gate_tuple[0], gate_tuple[0]+gate_tuple[1]))
                else:
                    gate = gate_tuple[2]
                    assert isinstance(gate, AdvanceGate)
                    if gate.awg_flag == True:
                        time_segment_number = gate.para_table['segment_number']
                        single_ion_data_dict = gate.para_table['data_per_ion'][ion_id]
                        for i in range(time_segment_number):
                            lt, rt = gate.para_table['time_intervals'][i]
                            temp_dict = {
                                'amp': single_ion_data_dict['amp'][i], 
                                'freq': single_ion_data_dict['freq'][i],
                                'phase': single_ion_data_dict['phase'][i]
                            }
                            t_list_single_ion.append((gate_tuple[0]+lt, gate_tuple[0]+rt))
                            func_list_single_ion = func_list_single_ion + [self.func_generator(temp_dict)]
                if isinstance(gate_tuple[2].wave_func, list):
                    for idx in range(len(gate_tuple[2].wave_func)):
                        if gate_tuple[2].relative_time:
                            relative_list_single_ion.append(gate_tuple[0])
                        else:
                            relative_list_single_ion.append(False)
                else:
                    relative_list_single_ion.append(gate_tuple[2].relative_time)
                
                    
            func_list.append(func_list_single_ion)
            t_list.append(t_list_single_ion)
            relative_list.append(relative_list_single_ion)

        return (func_list, t_list, relative_list)

    def generator_awg_waveform(self):
        func_list, t_list, relative_list = self.func_list_generator()
        data_list = []
        ion_id = 0
        for ion_f_list,ion_t_list,ion_relative_list in zip(func_list,t_list, relative_list):
            #start = ion_t_list[0]
            #data_temp = []
            #print(ion_id)
            tlist_temp = []
            flist_temp = np.array([])
            max_rt = 0
            #当前离子编号是ion_id, 下面的i是第i段函数
            for i in range(len(ion_t_list)):
                lt, rt = ion_t_list[i]
                lt, rt = round(lt,5), round(rt,5)
                if max_rt < rt:
                    max_rt = rt
                #print(lt, rt, ion_branch_list[i])
                t_temp_for_compute = np.linspace(round(lt-self.awg_trigger_time,5), round(rt-self.awg_trigger_time-1/self.awg_sampling_rate, 5), round((rt-lt)*self.awg_sampling_rate))
                # relative time from 0 to rt-lt
                if (ion_relative_list[i] != False):
                    if type(ion_relative_list[i]) == bool:
                        t_temp_for_compute = np.linspace(0, round(rt-lt-1/self.awg_sampling_rate,5), round((rt-lt)*self.awg_sampling_rate))
                    else:
                        t_temp_for_compute = np.linspace(round(lt-ion_relative_list[i],5), round(rt-ion_relative_list[i],5), round((rt-lt)*self.awg_sampling_rate))
                    #t_temp_for_compute = np.arange(0, round(rt-lt,5), 1/self.awg_sampling_rate)
                #print('ion,lt,rt,t_temp:', ion_id,lt,rt,len(t_temp_for_compute))
                tlist_temp.append((lt, rt))
                if i > 0:
                    if lt > tlist_temp[-2][1]:
                        num_zeroes = round((lt - tlist_temp[-2][1])*self.awg_sampling_rate)
                        #print(lt - tlist_temp[-2][1])
                        #print('padding zeroes: ion_id, i, duration ,num_zero_points', ion_id, i ,lt - tlist_temp[-2][1] ,num_zeroes)
                        if num_zeroes % 10 != 0:
                            # print(tlist_temp[-2][1],lt,num_zeroes)
                            pass
                        zero_padd = np.zeros(num_zeroes)
                        flist_temp = np.append(flist_temp,zero_padd)
                elif (lt >= self.awg_trigger_time): #i==0
                    num_zeros = round((lt - self.awg_trigger_time)*self.awg_sampling_rate)
                    zero_padd = np.zeros(num_zeros)
                    flist_temp = np.append(flist_temp,zero_padd)
                    #print(num_zeros)
                    #awg数据点从trigger之后才开始
                try:
                    #print(len(t_temp_for_compute))
                    flist_temp = np.append(flist_temp, ion_f_list[i](t_temp_for_compute))
                    #print(ion_f_list[i](t_temp_for_compute)[0:5])
                    #print('ion, data points: ', ion_id, len(flist_temp))
                except TypeError as e:
                    print(lt, rt)
                    print(e)
                    raise e
                #start = ion_t_list[i+1]
            flist_temp = np.array(flist_temp)
            tlist_temp_simple = tlist_temp
            #print((self.awg_trigger_time, round(max_rt,5), 1/self.awg_sampling_rate))
            if max_rt == 0:
                tlist_temp = []
            else:
                tlist_temp = np.linspace(self.awg_trigger_time, round(max_rt-1/self.awg_sampling_rate,5), round((max_rt - self.awg_trigger_time) * self.awg_sampling_rate))
            #print(len(tlist_temp), len(flist_temp))
            data_list.append((tlist_temp, flist_temp))
            ion_id += 1
        #print(data_list[0])
        return data_list

    def regenerate_sequence(self):
        args = tuple(self.input_list)
        self.clear(make_sure = True)
        self.inner_add_gates(args)
        self.set_para_flag = False
    
    def perform_sequence_awg(self):
        _temp_awg_data = self.generator_awg_waveform()

        max_awg_data_len = 0
        for i in range(len(_temp_awg_data)):
            if len(_temp_awg_data[i][1]) > max_awg_data_len:
                max_awg_data_len = len(_temp_awg_data[i][1])
        #print(max_awg_data_len)
        self.awg_data = []
        for i in range(len(_temp_awg_data)):
            temp_data = np.pad(_temp_awg_data[i][1], (0,max_awg_data_len-len(_temp_awg_data[i][1])))
            #self.awg_data.append(temp_data)
            self.awg_data.append(temp_data[250:max_awg_data_len])###########这里丢掉前250个数据点，来弥补awg的0.4us的响应延迟 
        self.awg_data = np.array(self.awg_data) ## This is what we need to send to the awg

    def perform_sequence_rtmq(self):
        # generate chapter data according to the datasheet and invoke the serial module
        time_index, start_point, end_point, branch_point, end_branch_positions = self.reformat_sequence()
        #print(end_sequence_positions)
        active_set = set()
        ll = len(time_index)
        result_list = []
        for idx, t in enumerate(time_index):
            detection_flag = False
            if end_point.get(t) != None:
                for item in end_point.get(t):
                    if isinstance(item[3], End) or isinstance(item[3], EndBranch):
                        continue
                    if isinstance(item[2], int):
                        active_set.remove((item[1], item[2], item[3]))
                    else:
                        active_set.remove((item[1], tuple(item[2]),  item[3]))
            if start_point.get(t) != None:
                for item in start_point.get(t):
                    if isinstance(item[3], End) or isinstance(item[3], EndBranch):
                        continue
                    if isinstance(item[3], Detection):
                        detection_flag = True
                    if isinstance(item[2], int):
                        active_set.add((item[1], item[2], item[3]))
                    else:
                        active_set.add((item[1], tuple(item[2]), item[3]))
            if branch_point.get(t) != None:
                branch_ion, tf_set, cycles = branch_point.get(t)
                assert len(tf_set) > 0
                keys = ['t','f']
                for k in keys:
                    if tf_set.get(k) != None:
                        kpoint = tf_set[k]
                        tmax = -1
                        for end_time in end_branch_positions:
                            if end_time > kpoint:
                                if (tmax > end_time) or (tmax == -1):
                                    tmax = end_time
                        tf_set[k] = (kpoint, tmax)
                tf_set['iter_times'] = cycles
                branch_point[t] = [branch_ion, tf_set]

            if t in end_branch_positions:
                #result_list.append([None,None,t,'end_branch'])
                pass
            if idx < ll-1:
                chapter_data = 0
                for active_pulse in active_set:
                    temp_str = active_pulse[2].chapter_data
                    temp_data = int(temp_str, 2)
                    chapter_data = chapter_data | temp_data
                chapter_str = str(bin(chapter_data))
                chapter_str = chapter_str[2:32]
                chapter_str = '0'*(24-len(chapter_str)) + chapter_str
                if detection_flag == False:
                    result_list.append([chapter_data, chapter_str, (t, time_index[idx+1]-t), None])
                elif branch_point.get(t) != None:    
                    result_list.append([chapter_data, chapter_str, (t, time_index[idx+1]-t), branch_point.get(t)])
                else:
                    result_list.append([chapter_data, chapter_str, (t, time_index[idx+1]-t), 'Pure_detection'])
            else:
                result_list.append([0,0,None,branch_point.get(t)])

        return result_list

    def perform_sequence(self):
        # generate chapter data according to the datasheet and invoke the serial module
        # need the explict rules of parallel pulses

        if self.set_para_flag == True:
            print("Caution! You have modified some parameters, but no sequence updating executed. If needed, try use the member function \
\'regenerate_sequence()\' to update it. This run will perform the sequence without the parameters updated")

        time_index, start_point, end_point, branch_p, end_p_positions = self.reformat_sequence()
        active_set = set()
        ll = len(time_index)
        result_list = []
        #print(start_point)
        #print(end_point)
        for idx, t in enumerate(time_index):
            if end_point.get(t) != None:
                for item in end_point.get(t):
                    # item: (sequence_index, pulse_type, ion_index, gate_object)
                    #active_set.remove(item[1])
                    if isinstance(item[2], int):
                        active_set.remove((item[1], item[2], item[3]))
                    else:
                        active_set.remove((item[1], tuple(item[2]),  item[3]))
            if start_point.get(t) != None:
                for item in start_point.get(t):
                    if isinstance(item[2], int):
                        active_set.add((item[1], item[2], item[3]))
                    else:
                        active_set.add((item[1], tuple(item[2]), item[3]))
            if idx < ll-1:
                chapter_data = 0
                for active_pulse in active_set:
                    #temp_str = self.get_chapter_data(active_pulse[0], active_pulse[1]).replace(' ','')
                    #Currently we would not distinguish the ion_index for FPGA gates
                    temp_str = active_pulse[2].chapter_data
                    #In later experiments, the chapter data of FPGA gates for each ion is bound to be distinct
                    #Like: ('Doppler',1),('Doppler',2),('Doppler',3),('EIT',1),('EIT',2),...,('RSB',1),...

                    temp_data = int(temp_str, 2)
                    chapter_data = chapter_data | temp_data
                chapter_str = str(bin(chapter_data))
                chapter_str = chapter_str[2:32]
                chapter_str = '0'*(24-len(chapter_str)) + chapter_str
                chapter_str = chapter_str[0:8] + ' ' + chapter_str[8:16] + ' ' + chapter_str[16:24]
                result_list.append([chapter_str, time_index[idx+1]-t])
                
        self.total_duration = sum([i[1] for i in result_list])
        return result_list

    def clear(self, make_sure = False):
        if make_sure == False:
            print("This operation will delete the sequence in current object, if you confirm \
                    to do that please set the argument \'make_sure\' to be True.")
        else:
            self.gate_sequence = []
            #del(self.gate_time)
            self.time_stamp = [0 for i in range(self.ion_number)]
            #self.gate_list = []
            # gate_list_per_ion存下每个离子对应的gate_list，list中每个元素都是一个三元组(start_time, duration, gate)
            # 其中gate是BaseGate的一个实例
            self.gate_list_per_ion = [[] for i in range(self.ion_number)]
            self.awg_trigger = False
            self.awg_trigger_time = -1
            self.current_time = -1
            self.labelled_pulse = {}



def plot_sequence(sequence, remap_time=True, ax: Optional[plt.Axes] = None, auto_size = False, given_size = None):
    """
    对于数据很多的图，如果想看清的话需要设置图片大小: plt.plot(figsize=(n,m))
    autosize == True时图片会自动设置长度，用于显示很长的sequence

    none_sequence = []
    sequence = copy.copy(sequence)
    for pulse_term in sequence:
        if pulse_term[3] == None:
            none_sequence.append(pulse_term)
    for none_pulse_term in none_sequence:
        sequence.remove(none_pulse_term)


    Parameters
    ----------
    sequence :Sequence
        需要画的序列
    remap_time : bool, optional
        是否将时间轴进行重新映射, by default True
    ax : Optional[plt.Axes], optional
        画图的坐标轴, by default None
    auto_size : bool, optional
        是否自动设置图片大小, by default False
    given_size : tuple, optional
        手动设置图片大小, by default None
    """
    
    new_sequence = []
    for gate_tuple in sequence:
        if not isinstance(gate_tuple[4], End) and not isinstance(gate_tuple[4], EndBranch):
            new_sequence.append((gate_tuple[0], round(gate_tuple[1],1), round(gate_tuple[2],1), gate_tuple[3], gate_tuple[4]))

    sequence = new_sequence

    moments_org2rmp, moments_rmp2org = remap_to_indices(
        moment
        for (_, start_time, duration, _, _) in sequence
        for moment in (start_time, start_time + duration)
    )

    #print(moments_org2rmp, moments_rmp2org)
    xlen = len(moments_rmp2org)
    if auto_size == True:
        plt.figure(figsize=(int(xlen*1.5),5))
    elif given_size != None:
        plt.figure(figsize=given_size)

    sequence = tuple(sequence)
    ax = plt.gca() if ax is None else ax


    line1, line2 = None, None

    ion_org2rmp, ion_rmp2org = remap_to_indices(
        ion
        for (ions, start_time, duration, _, _) in sequence
        for ion in iter_int_or_tuple(ions)
    )

    for ions, start_time, duration, pulse_type, pulse_instance in sequence:
        #start_time = round(start_time, 3)
        ions = sorted(list(iter_int_or_tuple(ions)))

        if remap_time:
            remapped_start_moment = moments_org2rmp[start_time]
            remapped_end_moment = moments_org2rmp[start_time + duration]
        else:
            remapped_start_moment = start_time
            remapped_end_moment = start_time + duration

        polygon_xy = []
        for ion in ions:
            remapped_ion = ion_org2rmp[ion]

            l = remapped_start_moment
            r = remapped_end_moment
            w = r - l
            h = 4 / 5
            cx = (l + r) / 2
            cy = remapped_ion

            rect = plt.Rectangle((l, cy - h / 2), w, h,
                facecolor='white', edgecolor='black', linewidth=3,
                zorder=2.5)
            if pulse_instance.awg_flag == True:
                rect = plt.Rectangle((l, cy - h / 2), w, h,
                facecolor='white', edgecolor='blue', linewidth=3,
                zorder=2.5)
                line2 = rect
            else:
                line1 = rect

            ax.add_patch(rect)

            text = plt.Text(cx, cy, pulse_type,
                verticalalignment='center', horizontalalignment='center',
                wrap=True, color='black',
                zorder=2.5)
            ax.add_artist(text)

            polygon_xy.append((cx, cy))

        if len(ions) > 1:
            polygon = plt.Polygon(polygon_xy, False,
                edgecolor='black', linewidth=3,
                zorder=2.4)
            ax.add_patch(polygon)

    ax.set_xlabel("time")

    if remap_time:
        padding_x = 3 / 5
        ax.set_xlim(0 - padding_x, len(moments_rmp2org) - 1 + padding_x)
    else:
        padding_x = (max(moments_rmp2org) - min(moments_rmp2org)) / 20
        ax.set_xlim(min(moments_rmp2org) - padding_x, max(moments_rmp2org) + padding_x)

    ax.grid(axis='x', color='grey', ls='--')
    if remap_time:
        ax.set_xticks(range(len(moments_rmp2org)))
    else:
        ax.set_xticks(moments_rmp2org)
    ax.set_xticklabels(moments_rmp2org)

    ax.set_ylabel("ion")

    padding_y = 3 / 5
    ax.set_ylim(len(ion_rmp2org) - 1 + padding_y, -padding_y)
    ax.grid(axis='y', color='black')
    ax.set_yticks(range(len(ion_rmp2org)))
    ax.set_yticklabels(ion_rmp2org)

    ax.set_frame_on(False)

    #ax.legend(['fpga--black','awg--blue','1','1','1','1','1','1'])
    #box = ax.get_position()
    #ax.set_position([box.x0, box.y0, box.width, box.height*0.8])
    if line1 != None and line2 != None:
        ax.legend((line1,line2),('fpga','awg'), loc='upper left', bbox_to_anchor = (0,1.15), ncol=3)
    elif line1 != None:
        ax.legend([line1],['fpga'], loc='upper left', bbox_to_anchor = (0,1.15), ncol=3)
    elif line2 != None:
        ax.legend([line2],['awg'], loc='upper left', bbox_to_anchor = (0,1.15), ncol=3)


def remap_to_indices(items, key=None):
    items_set = set(items)
    items_sorted = sorted(items_set, key=key)
    moments_dict = {t: i for i, t in enumerate(items_sorted)}

    org2rmp = moments_dict
    rmp2org = items_sorted
    return org2rmp, rmp2org


def iter_int_or_tuple(item):
    try:
        for sub_item in item:
            yield sub_item
    except TypeError:
        yield item

def iter_gate_or_tuple_or_list(item):
    if isinstance(item, list):
        for sub_item in item:
            yield(sub_item)
    else:
        yield item

# test_the sequence
if __name__ == '__main__':
    print(1)
    print(2)